fix(worked-before): a prefixed call is one operator, not a whole country
Reported from a screenshot: working ZA/OE8NDR, the worked-before list showed ZA/IZ2DPX and ZA/IW2JOP beside him and the header counted all four as contacts "with this call". callMatch folded portable forms together by taking the part before the slash. That is right when the slash carries a suffix — RK3DWA/3, RK3DWA/P are one person — and exactly wrong when it carries a prefix: ZA/OE8NDR became the station "ZA", and the predicate `callsign LIKE 'ZA/%'` then selected every other visitor to Albania. The operator's own call is now picked out properly: drop the known qualifiers (P, M, MM, AM, QRP, a bare call-area digit) and of what remains take the longest part — a country prefix is short by nature, ZA, F, KH6, VP2E, and a callsign is not. The predicate matches the prefixed forms too, so ZA/OE8NDR and a plain OE8NDR still find each other, which is the whole point of the fold. A base under three characters falls back to an exact match: a malformed entry must not produce a LIKE that selects half the logbook.
This commit is contained in:
@@ -70,12 +70,12 @@ func TestStatsNoNilSlices(t *testing.T) {
|
||||
}
|
||||
|
||||
// Contest metrics over a window. The two traps:
|
||||
// 1. "Best hour" must be the best ROLLING 60 minutes, not the best clock hour —
|
||||
// a run straddling 13:45–14:45 is invisible to clock-hour bucketing, and the
|
||||
// rolling figure is the one contesters quote.
|
||||
// 2. Both rates must be reported: QSOs ÷ whole window (honest, breaks included)
|
||||
// AND QSOs ÷ hours actually operated. Quoting only the latter is how an
|
||||
// 8-hour effort gets sold as a 48-hour score.
|
||||
// 1. "Best hour" must be the best ROLLING 60 minutes, not the best clock hour —
|
||||
// a run straddling 13:45–14:45 is invisible to clock-hour bucketing, and the
|
||||
// rolling figure is the one contesters quote.
|
||||
// 2. Both rates must be reported: QSOs ÷ whole window (honest, breaks included)
|
||||
// AND QSOs ÷ hours actually operated. Quoting only the latter is how an
|
||||
// 8-hour effort gets sold as a 48-hour score.
|
||||
func TestContestPeriodMetrics(t *testing.T) {
|
||||
base := time.Date(2026, 5, 30, 12, 0, 0, 0, time.UTC)
|
||||
at := func(min int) time.Time { return base.Add(time.Duration(min) * time.Minute) }
|
||||
@@ -90,8 +90,8 @@ func TestContestPeriodMetrics(t *testing.T) {
|
||||
times = append(times, entry{t: at(240 + i*5), op: "F5XYZ"}) // 16:00 …
|
||||
}
|
||||
|
||||
from := base // 12:00
|
||||
to := base.Add(6 * time.Hour) // 18:00 → a 6-hour window
|
||||
from := base // 12:00
|
||||
to := base.Add(6 * time.Hour) // 18:00 → a 6-hour window
|
||||
var s Stats
|
||||
s.periodMetrics(times, from, to, time.Time{}, time.Time{})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user