From 18b179beb9040992a909c50ea744fc5d69b1ed4f Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Sun, 22 Mar 2026 10:53:42 +0100 Subject: [PATCH] Expose admission metadata in debug output and tests --- cmd/sdrd/decision_compact.go | 28 ++++++++++++++---------- internal/pipeline/decision_queue_test.go | 12 +++++++++- internal/pipeline/scheduler_test.go | 9 ++++++++ 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/cmd/sdrd/decision_compact.go b/cmd/sdrd/decision_compact.go index ec0f28d..288fb9c 100644 --- a/cmd/sdrd/decision_compact.go +++ b/cmd/sdrd/decision_compact.go @@ -3,24 +3,28 @@ package main import "sdr-wideband-suite/internal/pipeline" type compactDecision struct { - ID int64 `json:"id"` - Class string `json:"class,omitempty"` - Record bool `json:"record"` - Decode bool `json:"decode"` - Reason string `json:"reason,omitempty"` - Candidate pipeline.Candidate `json:"candidate"` + ID int64 `json:"id"` + Class string `json:"class,omitempty"` + Record bool `json:"record"` + Decode bool `json:"decode"` + Reason string `json:"reason,omitempty"` + RecordAdmission *pipeline.PriorityAdmission `json:"record_admission,omitempty"` + DecodeAdmission *pipeline.PriorityAdmission `json:"decode_admission,omitempty"` + Candidate pipeline.Candidate `json:"candidate"` } func compactDecisions(decisions []pipeline.SignalDecision) []compactDecision { out := make([]compactDecision, 0, len(decisions)) for _, d := range decisions { out = append(out, compactDecision{ - ID: d.Candidate.ID, - Class: d.Class, - Record: d.ShouldRecord, - Decode: d.ShouldAutoDecode, - Reason: d.Reason, - Candidate: d.Candidate, + ID: d.Candidate.ID, + Class: d.Class, + Record: d.ShouldRecord, + Decode: d.ShouldAutoDecode, + Reason: d.Reason, + RecordAdmission: d.RecordAdmission, + DecodeAdmission: d.DecodeAdmission, + Candidate: d.Candidate, }) } return out diff --git a/internal/pipeline/decision_queue_test.go b/internal/pipeline/decision_queue_test.go index becc731..942893e 100644 --- a/internal/pipeline/decision_queue_test.go +++ b/internal/pipeline/decision_queue_test.go @@ -1,6 +1,7 @@ package pipeline import ( + "strings" "testing" "time" ) @@ -25,7 +26,7 @@ func TestDecisionQueueDropsByBudget(t *testing.T) { allowed++ continue } - if d.Reason != DecisionReasonQueueRecord && d.Reason != DecisionReasonQueueDecode { + if !strings.HasPrefix(d.Reason, DecisionReasonQueueRecord) && !strings.HasPrefix(d.Reason, DecisionReasonQueueDecode) { t.Fatalf("unexpected decision reason: %s", d.Reason) } } @@ -56,6 +57,12 @@ func TestDecisionQueueEnforcesBudgets(t *testing.T) { if decisions[2].ShouldRecord { t.Fatalf("expected mid SNR decision to be budgeted off by record budget") } + if decisions[1].RecordAdmission == nil || decisions[1].RecordAdmission.Class != AdmissionClassAdmit { + t.Fatalf("expected admitted record admission, got %+v", decisions[1].RecordAdmission) + } + if decisions[0].RecordAdmission == nil || decisions[0].RecordAdmission.Class != AdmissionClassDefer { + t.Fatalf("expected deferred record admission, got %+v", decisions[0].RecordAdmission) + } } func TestDecisionQueueHoldKeepsSelection(t *testing.T) { @@ -84,4 +91,7 @@ func TestDecisionQueueHoldKeepsSelection(t *testing.T) { if decisions[0].ShouldRecord || decisions[0].ShouldAutoDecode { t.Fatalf("expected candidate 1 to remain queued behind hold") } + if decisions[1].RecordAdmission == nil || decisions[1].RecordAdmission.Class != AdmissionClassHold { + t.Fatalf("expected record admission hold class, got %+v", decisions[1].RecordAdmission) + } } diff --git a/internal/pipeline/scheduler_test.go b/internal/pipeline/scheduler_test.go index 7843423..7b84c4e 100644 --- a/internal/pipeline/scheduler_test.go +++ b/internal/pipeline/scheduler_test.go @@ -287,10 +287,16 @@ func TestAdmitRefinementPlanAppliesBudget(t *testing.T) { if item2 == nil || item2.Status != RefinementStatusAdmitted { t.Fatalf("expected candidate 2 admitted, got %+v", item2) } + if item2.Admission == nil || item2.Admission.Class != AdmissionClassAdmit || item2.Admission.Tier == "" { + t.Fatalf("expected admission class/tier on admitted item, got %+v", item2.Admission) + } item3 := findWorkItem(res.WorkItems, 3) if item3 == nil || item3.Status != RefinementStatusSkipped { t.Fatalf("expected candidate 3 skipped, got %+v", item3) } + if item3.Admission == nil || item3.Admission.Class != AdmissionClassDefer { + t.Fatalf("expected deferred admission class on skipped item, got %+v", item3.Admission) + } } func TestAdmitRefinementPlanDisplacedByHold(t *testing.T) { @@ -309,6 +315,9 @@ func TestAdmitRefinementPlanDisplacedByHold(t *testing.T) { if item2 == nil || item2.Status != RefinementStatusDisplaced { t.Fatalf("expected higher priority candidate displaced, got %+v", item2) } + if item2.Admission == nil || item2.Admission.Class != AdmissionClassDisplace { + t.Fatalf("expected displaced admission class, got %+v", item2.Admission) + } } func TestRefinementStrategyUsesProfile(t *testing.T) {