fix(stats): confirmed outranks worked in the band/mode matrix

Reported by VK4DX with the case that names itself: YB confirmed on 20m
digital, painted blue, because one unconfirmed YB station had also been
worked on that slot.

The ladder ran call_c > call_w > dxcc_c > dxcc_w, so a callsign worked and
not confirmed beat an entity CONFIRMED on the same band and mode. The grid
answers 'what do I still need here', and a confirmed entity needs nothing
whoever was worked afterwards — so the order is now call_c > dxcc_c >
call_w > dxcc_w.

Two things made it easy to get wrong, and both are fixed rather than
merely corrected. The code was a run of assignments where the later test
silently overwrote the earlier, so 'call worked' erased 'entity
confirmed'; it now takes the maximum. And the rule lived inside a scan
loop where nothing could reach it, so it is lifted into bandStatusCode
with a table test — including this exact case, and one that fails if
anybody reorders the constants.
This commit is contained in:
2026-08-26 10:53:36 +02:00
parent 08905a1680
commit dc8f9c5099
3 changed files with 120 additions and 21 deletions
+58
View File
@@ -0,0 +1,58 @@
package qso
import "testing"
// The colour of a matrix cell is a claim about what the operator still needs,
// and it was wrong for several releases: a callsign worked and not confirmed
// outranked an entity CONFIRMED on the same band and mode, so a slot that was
// finished showed as unfinished.
//
// Reported by VK4DX with the case that names itself: YB confirmed on 20m
// digital, shown blue, because one unconfirmed YB station had also been worked
// there.
func TestBandStatusConfirmedOutranksWorked(t *testing.T) {
cases := []struct {
name string
callWorked, callConfirmed, entityConfirm bool
want string
}{
{"entity worked only", false, false, false, "dxcc_w"},
{"this call worked, nothing confirmed", true, false, false, "call_w"},
// The regression, in one line.
{"entity confirmed, this call worked but not confirmed", true, false, true, "dxcc_c"},
{"entity confirmed, this call not worked here", false, false, true, "dxcc_c"},
{"this call confirmed", true, true, true, "call_c"},
// A confirmed contact with this call implies it was worked, but the flags
// arrive from separate SQL aggregates and nothing guarantees the pair.
{"call confirmed without the worked flag", false, true, true, "call_c"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := bandStatusNames[bandStatusCode(c.callWorked, c.callConfirmed, c.entityConfirm)]
if got != c.want {
t.Errorf("bandStatusCode(worked=%v, callConf=%v, entityConf=%v) = %s, want %s",
c.callWorked, c.callConfirmed, c.entityConfirm, got, c.want)
}
})
}
}
// The ladder itself, stated once: every confirmed code must beat every worked
// code. A future edit that reorders the constants fails here rather than in a
// screenshot from an operator.
func TestBandStatusLadderPutsConfirmedAbove(t *testing.T) {
for _, worked := range []int{stDxccW, stCallW} {
for _, confirmed := range []int{stDxccC, stCallC} {
if confirmed <= worked {
t.Errorf("%s (%d) does not outrank %s (%d)",
bandStatusNames[confirmed], confirmed, bandStatusNames[worked], worked)
}
}
}
if stCallC <= stDxccC {
t.Error("call_c must outrank dxcc_c: the callsign is the more specific claim")
}
if stCallW <= stDxccW {
t.Error("call_w must outrank dxcc_w for the same reason")
}
}