| @@ -30,6 +30,26 @@ func TestHoldPolicyDigitalBiasesDecode(t *testing.T) { | |||
| } | |||
| } | |||
| func TestHoldPolicyIntentOverrides(t *testing.T) { | |||
| policy := Policy{DecisionHoldMs: 1000, Intent: "archive-and-triage"} | |||
| hold := HoldPolicyFromPolicy(policy) | |||
| if hold.RecordMs <= hold.BaseMs { | |||
| t.Fatalf("expected archive intent to extend record hold, got %d vs %d", hold.RecordMs, hold.BaseMs) | |||
| } | |||
| if !containsReason(hold.Reasons, HoldReasonIntentArchive) { | |||
| t.Fatalf("expected intent archive reason, got %+v", hold.Reasons) | |||
| } | |||
| policy = Policy{DecisionHoldMs: 1000, Intent: "decode-digital"} | |||
| hold = HoldPolicyFromPolicy(policy) | |||
| if hold.DecodeMs <= hold.BaseMs { | |||
| t.Fatalf("expected decode intent to extend decode hold, got %d vs %d", hold.DecodeMs, hold.BaseMs) | |||
| } | |||
| if !containsReason(hold.Reasons, HoldReasonIntentDecode) { | |||
| t.Fatalf("expected intent decode reason, got %+v", hold.Reasons) | |||
| } | |||
| } | |||
| func TestAdmitRefinementPlanNoCandidatesReason(t *testing.T) { | |||
| res := AdmitRefinementPlan(RefinementPlan{}, Policy{}, time.Now(), &RefinementHold{Active: map[int64]time.Time{}}) | |||
| if res.Admission.Reason != ReasonAdmissionNoCandidates { | |||
| @@ -138,6 +138,39 @@ func TestDecisionQueueHighTierHoldProtected(t *testing.T) { | |||
| } | |||
| } | |||
| func TestDecisionQueueFamilyPriorityProtectsHold(t *testing.T) { | |||
| arbiter := NewArbiter() | |||
| policy := Policy{DecisionHoldMs: 500, SignalPriorities: []string{"digital"}} | |||
| budget := BudgetModel{Record: BudgetQueue{Max: 1}} | |||
| now := time.Now() | |||
| decisions := []SignalDecision{ | |||
| {Candidate: Candidate{ID: 1, SNRDb: 5, Hint: "digital"}, ShouldRecord: true}, | |||
| } | |||
| arbiter.ApplyDecisions(decisions, budget, now, policy) | |||
| if !decisions[0].ShouldRecord { | |||
| t.Fatalf("expected candidate 1 to be selected initially") | |||
| } | |||
| decisions = []SignalDecision{ | |||
| {Candidate: Candidate{ID: 1, SNRDb: 5, Hint: "digital"}, ShouldRecord: true}, | |||
| {Candidate: Candidate{ID: 2, SNRDb: 35, Hint: "voice"}, ShouldRecord: true}, | |||
| } | |||
| arbiter.ApplyDecisions(decisions, budget, now.Add(100*time.Millisecond), policy) | |||
| if !decisions[0].ShouldRecord { | |||
| t.Fatalf("expected family-priority hold to keep candidate 1") | |||
| } | |||
| if decisions[1].ShouldRecord { | |||
| t.Fatalf("expected candidate 2 to remain deferred behind family hold") | |||
| } | |||
| if decisions[0].RecordAdmission == nil || decisions[0].RecordAdmission.FamilyRank != 1 { | |||
| t.Fatalf("expected family rank on admission, got %+v", decisions[0].RecordAdmission) | |||
| } | |||
| if decisions[0].RecordAdmission == nil || decisions[0].RecordAdmission.TierFloor != PriorityTierHigh { | |||
| t.Fatalf("expected tier floor on admission, got %+v", decisions[0].RecordAdmission) | |||
| } | |||
| } | |||
| func TestDecisionQueueOpportunisticDisplacement(t *testing.T) { | |||
| arbiter := NewArbiter() | |||
| policy := Policy{DecisionHoldMs: 500} | |||
| @@ -145,6 +145,28 @@ func TestScheduleCandidatesPriorityBoost(t *testing.T) { | |||
| } | |||
| } | |||
| func TestScheduleCandidatesFamilyTierFloor(t *testing.T) { | |||
| policy := Policy{MaxRefinementJobs: 2, MinCandidateSNRDb: 0, SignalPriorities: []string{"digital", "wfm"}} | |||
| cands := []Candidate{ | |||
| {ID: 1, SNRDb: 1, Hint: "digital-burst"}, | |||
| {ID: 2, SNRDb: 20, Hint: "voice"}, | |||
| } | |||
| plan := BuildRefinementPlan(cands, policy) | |||
| item := findScheduled(plan.Ranked, 1) | |||
| if item == nil { | |||
| t.Fatalf("expected ranked candidate 1") | |||
| } | |||
| if item.Family != "digital" || item.FamilyRank != 1 { | |||
| t.Fatalf("expected digital family rank 1, got family=%s rank=%d", item.Family, item.FamilyRank) | |||
| } | |||
| if item.TierFloor != PriorityTierHigh { | |||
| t.Fatalf("expected tier floor high, got %s", item.TierFloor) | |||
| } | |||
| if priorityTierRank(item.Tier) < priorityTierRank(PriorityTierHigh) { | |||
| t.Fatalf("expected tier to be raised by family floor, got %s", item.Tier) | |||
| } | |||
| } | |||
| func TestScheduleCandidatesEvidenceBoost(t *testing.T) { | |||
| policy := Policy{MaxRefinementJobs: 2, MinCandidateSNRDb: 0} | |||
| single := Candidate{ | |||
| @@ -386,3 +408,12 @@ func findWorkItem(items []RefinementWorkItem, id int64) *RefinementWorkItem { | |||
| } | |||
| return nil | |||
| } | |||
| func findScheduled(items []ScheduledCandidate, id int64) *ScheduledCandidate { | |||
| for i := range items { | |||
| if items[i].Candidate.ID == id { | |||
| return &items[i] | |||
| } | |||
| } | |||
| return nil | |||
| } | |||