|
- package main
-
- import (
- "sort"
- "strings"
-
- "sdr-wideband-suite/internal/pipeline"
- )
-
- type WindowSummary struct {
- Refinement *RefinementWindowStats `json:"refinement,omitempty"`
- MonitorWindows []pipeline.MonitorWindowStats `json:"monitor_windows,omitempty"`
- Outcomes *WindowOutcomeSummary `json:"outcomes,omitempty"`
- }
-
- type WindowOutcomeSummary struct {
- Windows []MonitorWindowOutcome `json:"windows,omitempty"`
- Zones []MonitorZoneOutcome `json:"zones,omitempty"`
- }
-
- type MonitorWindowOutcome struct {
- Index int `json:"index"`
- Label string `json:"label,omitempty"`
- Zone string `json:"zone,omitempty"`
- Refinement OutcomeCounts `json:"refinement,omitempty"`
- Record OutcomeCounts `json:"record,omitempty"`
- Decode OutcomeCounts `json:"decode,omitempty"`
- }
-
- type MonitorZoneOutcome struct {
- Zone string `json:"zone"`
- Refinement OutcomeCounts `json:"refinement,omitempty"`
- Record OutcomeCounts `json:"record,omitempty"`
- Decode OutcomeCounts `json:"decode,omitempty"`
- }
-
- type OutcomeCounts struct {
- Admit int `json:"admit,omitempty"`
- Hold int `json:"hold,omitempty"`
- Displace int `json:"displace,omitempty"`
- Defer int `json:"defer,omitempty"`
- Drop int `json:"drop,omitempty"`
- Enabled int `json:"enabled,omitempty"`
- }
-
- func (o *OutcomeCounts) addClass(class string) {
- switch class {
- case pipeline.AdmissionClassAdmit:
- o.Admit++
- case pipeline.AdmissionClassHold:
- o.Hold++
- case pipeline.AdmissionClassDisplace:
- o.Displace++
- case pipeline.AdmissionClassDefer:
- o.Defer++
- case pipeline.AdmissionClassDrop:
- o.Drop++
- }
- }
-
- func (o *OutcomeCounts) addEnabled(enabled bool) {
- if enabled {
- o.Enabled++
- }
- }
-
- func (o OutcomeCounts) hasAny() bool {
- return o.Admit > 0 || o.Hold > 0 || o.Displace > 0 || o.Defer > 0 || o.Drop > 0 || o.Enabled > 0
- }
-
- func (o *OutcomeCounts) addTotals(in OutcomeCounts) {
- o.Admit += in.Admit
- o.Hold += in.Hold
- o.Displace += in.Displace
- o.Defer += in.Defer
- o.Drop += in.Drop
- o.Enabled += in.Enabled
- }
-
- func buildWindowSummary(plan pipeline.RefinementPlan, refinementWindows []pipeline.RefinementWindow, candidates []pipeline.Candidate, workItems []pipeline.RefinementWorkItem, decisions []pipeline.SignalDecision) *WindowSummary {
- refinementStats := buildWindowStats(refinementWindows)
- monitorSummary := buildMonitorWindowSummary(plan.MonitorWindows, plan.MonitorWindowStats, candidates)
- outcomes := buildWindowOutcomeSummary(plan.MonitorWindows, plan.MonitorWindowStats, workItems, decisions)
- if refinementStats == nil && len(monitorSummary) == 0 && outcomes == nil {
- return nil
- }
- return &WindowSummary{
- Refinement: refinementStats,
- MonitorWindows: monitorSummary,
- Outcomes: outcomes,
- }
- }
-
- func buildMonitorWindowSummary(windows []pipeline.MonitorWindow, stats []pipeline.MonitorWindowStats, candidates []pipeline.Candidate) []pipeline.MonitorWindowStats {
- var summary []pipeline.MonitorWindowStats
- switch {
- case len(stats) > 0:
- summary = append([]pipeline.MonitorWindowStats(nil), stats...)
- case len(windows) > 0:
- summary = make([]pipeline.MonitorWindowStats, 0, len(windows))
- for _, win := range windows {
- summary = append(summary, pipeline.MonitorWindowStats{
- Index: win.Index,
- Label: win.Label,
- Zone: win.Zone,
- Source: win.Source,
- StartHz: win.StartHz,
- EndHz: win.EndHz,
- CenterHz: win.CenterHz,
- SpanHz: win.SpanHz,
- Priority: win.Priority,
- PriorityBias: win.PriorityBias,
- RecordBias: win.RecordBias,
- DecodeBias: win.DecodeBias,
- AutoRecord: win.AutoRecord,
- AutoDecode: win.AutoDecode,
- })
- }
- default:
- return nil
- }
-
- if len(candidates) > 0 && len(summary) > 0 {
- windowsForMatch := windows
- if len(windowsForMatch) == 0 {
- windowsForMatch = monitorWindowsFromStats(summary)
- }
- if len(windowsForMatch) > 0 {
- counts := map[int]int{}
- total := 0
- for _, cand := range candidates {
- matches := cand.MonitorMatches
- if len(matches) == 0 {
- matches = pipeline.MonitorWindowMatchesForCandidate(windowsForMatch, cand)
- }
- for _, match := range matches {
- counts[match.Index]++
- total++
- }
- }
- if total > 0 {
- for i := range summary {
- if summary[i].Candidates == 0 {
- summary[i].Candidates = counts[summary[i].Index]
- }
- }
- }
- }
- }
-
- sort.Slice(summary, func(i, j int) bool {
- return summary[i].Index < summary[j].Index
- })
- return summary
- }
-
- func buildWindowOutcomeSummary(windows []pipeline.MonitorWindow, stats []pipeline.MonitorWindowStats, workItems []pipeline.RefinementWorkItem, decisions []pipeline.SignalDecision) *WindowOutcomeSummary {
- base := windows
- if len(base) == 0 && len(stats) > 0 {
- base = monitorWindowsFromStats(stats)
- }
- if len(base) == 0 {
- return nil
- }
- outcomes := make([]MonitorWindowOutcome, 0, len(base))
- index := make(map[int]int, len(base))
- for _, win := range base {
- outcomes = append(outcomes, MonitorWindowOutcome{
- Index: win.Index,
- Label: win.Label,
- Zone: win.Zone,
- })
- index[win.Index] = len(outcomes) - 1
- }
- windowsForMatch := base
- if len(workItems) > 0 {
- for _, item := range workItems {
- class := outcomeClassForWorkItem(item)
- if class == "" {
- continue
- }
- matches := item.Candidate.MonitorMatches
- if len(matches) == 0 {
- matches = pipeline.MonitorWindowMatchesForCandidate(windowsForMatch, item.Candidate)
- }
- for _, match := range matches {
- if idx, ok := index[match.Index]; ok {
- outcomes[idx].Refinement.addClass(class)
- }
- }
- }
- }
- if len(decisions) > 0 {
- for _, decision := range decisions {
- if match := decisionWindowMatch(decision, "record"); match != nil {
- if idx, ok := index[match.Index]; ok {
- outcomes[idx].Record.addEnabled(decision.ShouldRecord)
- if decision.RecordAdmission != nil {
- outcomes[idx].Record.addClass(decision.RecordAdmission.Class)
- }
- }
- }
- if match := decisionWindowMatch(decision, "decode"); match != nil {
- if idx, ok := index[match.Index]; ok {
- outcomes[idx].Decode.addEnabled(decision.ShouldAutoDecode)
- if decision.DecodeAdmission != nil {
- outcomes[idx].Decode.addClass(decision.DecodeAdmission.Class)
- }
- }
- }
- }
- }
- hasOutcome := false
- for i := range outcomes {
- if outcomes[i].Refinement.hasAny() || outcomes[i].Record.hasAny() || outcomes[i].Decode.hasAny() {
- hasOutcome = true
- break
- }
- }
- if !hasOutcome {
- return nil
- }
- sort.Slice(outcomes, func(i, j int) bool {
- return outcomes[i].Index < outcomes[j].Index
- })
- zoneIndex := map[string]int{}
- zones := make([]MonitorZoneOutcome, 0, len(outcomes))
- for _, outcome := range outcomes {
- zone := strings.TrimSpace(outcome.Zone)
- if zone == "" {
- continue
- }
- idx, ok := zoneIndex[zone]
- if !ok {
- zones = append(zones, MonitorZoneOutcome{Zone: zone})
- idx = len(zones) - 1
- zoneIndex[zone] = idx
- }
- zones[idx].Refinement.addTotals(outcome.Refinement)
- zones[idx].Record.addTotals(outcome.Record)
- zones[idx].Decode.addTotals(outcome.Decode)
- }
- if len(zones) > 0 {
- sort.Slice(zones, func(i, j int) bool {
- return zones[i].Zone < zones[j].Zone
- })
- }
- return &WindowOutcomeSummary{Windows: outcomes, Zones: zones}
- }
-
- func outcomeClassForWorkItem(item pipeline.RefinementWorkItem) string {
- if item.Admission != nil && item.Admission.Class != "" {
- return item.Admission.Class
- }
- switch item.Status {
- case pipeline.RefinementStatusAdmitted, pipeline.RefinementStatusRunning, pipeline.RefinementStatusCompleted:
- return pipeline.AdmissionClassAdmit
- case pipeline.RefinementStatusDisplaced:
- return pipeline.AdmissionClassDisplace
- case pipeline.RefinementStatusSkipped:
- return pipeline.AdmissionClassDefer
- case pipeline.RefinementStatusDropped:
- return pipeline.AdmissionClassDrop
- default:
- return ""
- }
- }
-
- func decisionWindowMatch(decision pipeline.SignalDecision, action string) *pipeline.MonitorWindowMatch {
- switch strings.ToLower(strings.TrimSpace(action)) {
- case "record":
- if decision.RecordWindow != nil {
- return decision.RecordWindow
- }
- case "decode":
- if decision.DecodeWindow != nil {
- return decision.DecodeWindow
- }
- }
- return decision.MonitorDetail
- }
-
- func monitorWindowsFromStats(stats []pipeline.MonitorWindowStats) []pipeline.MonitorWindow {
- if len(stats) == 0 {
- return nil
- }
- windows := make([]pipeline.MonitorWindow, 0, len(stats))
- for _, stat := range stats {
- windows = append(windows, pipeline.MonitorWindow{
- Index: stat.Index,
- Label: stat.Label,
- Zone: stat.Zone,
- Source: stat.Source,
- StartHz: stat.StartHz,
- EndHz: stat.EndHz,
- CenterHz: stat.CenterHz,
- SpanHz: stat.SpanHz,
- Priority: stat.Priority,
- PriorityBias: stat.PriorityBias,
- RecordBias: stat.RecordBias,
- DecodeBias: stat.DecodeBias,
- AutoRecord: stat.AutoRecord,
- AutoDecode: stat.AutoDecode,
- })
- }
- return windows
- }
|