feat(chase): the global hunt — new, or new plus unconfirmed, every category
The gap between worked and confirmed becomes visible everywhere. A Chase setting on the DX Cluster page picks the mode; in new-plus-unconfirmed the verdicts for DXCC/band/mode/slot, prefix, county and state are judged against CONFIRMED-only ledgers built from the operator's chosen sources (LoTW, card, eQSL, QRZ.com — HRDLog has no confirmation field in ADIF to offer), and a need that exists only because a QSL never arrived is flagged Unconf: the cluster and decode badges render it dimmed, the convention the grid hunt introduced. The grid's own Chase selector folds into the global one; its square-matching scope stays where it was, being grid-specific. One snapshot key change rebuilds the cache when any of it flips.
This commit is contained in:
+66
-4
@@ -2657,10 +2657,20 @@ type EntitySlot struct {
|
||||
// Modes/Slots key — pass GroupDigitalMode to collapse all digital modes into
|
||||
// one bucket. Callers must normalise their lookup mode the same way.
|
||||
func (r *Repo) EntitySlotMap(ctx context.Context, keyFor func(call string, storedDXCC int, country string) int, normMode func(string) string) (map[int]*EntitySlot, error) {
|
||||
return r.EntitySlotMapPred(ctx, keyFor, normMode, "")
|
||||
}
|
||||
|
||||
// EntitySlotMapPred is EntitySlotMap over only the QSOs matching pred — the
|
||||
// confirmed-only ledger the "chase unconfirmed too" mode judges against.
|
||||
func (r *Repo) EntitySlotMapPred(ctx context.Context, keyFor func(call string, storedDXCC int, country string) int, normMode func(string) string, pred string) (map[int]*EntitySlot, error) {
|
||||
where := ""
|
||||
if pred != "" {
|
||||
where = " AND " + pred
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT callsign, coalesce(dxcc,0), lower(coalesce(country,'')), lower(band), upper(mode) FROM qso
|
||||
WHERE band IS NOT NULL AND band != ''
|
||||
AND mode IS NOT NULL AND mode != ''`)
|
||||
AND mode IS NOT NULL AND mode != ''`+where)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2710,8 +2720,18 @@ func (r *Repo) EntitySlotMap(ctx context.Context, keyFor func(call string, store
|
||||
// One pass, used by the cluster spot colouring to flag "already worked this
|
||||
// exact call" regardless of band/mode — Log4OM/RUMlogNG-style call highlight.
|
||||
func (r *Repo) WorkedCallsigns(ctx context.Context) (map[string]struct{}, error) {
|
||||
return r.WorkedCallsignsPred(ctx, "")
|
||||
}
|
||||
|
||||
// WorkedCallsignsPred restricts the callsign ledger to QSOs matching pred —
|
||||
// the confirmed-prefix (WPX) chase reads its prefixes off this set.
|
||||
func (r *Repo) WorkedCallsignsPred(ctx context.Context, pred string) (map[string]struct{}, error) {
|
||||
where := ""
|
||||
if pred != "" {
|
||||
where = " AND " + pred
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT upper(callsign) FROM qso WHERE callsign != ''`)
|
||||
`SELECT DISTINCT upper(callsign) FROM qso WHERE callsign != ''`+where)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2732,9 +2752,18 @@ func (r *Repo) WorkedCallsigns(ctx context.Context) (map[string]struct{}, error)
|
||||
// the award package here). Only US-entity QSOs (DXCC 291/110/6) with a county
|
||||
// are considered. Empty keys (unresolvable state/county) are skipped.
|
||||
func (r *Repo) WorkedCountyKeys(ctx context.Context, keyFn func(state, cnty string) string) (map[string]struct{}, error) {
|
||||
return r.WorkedCountyKeysPred(ctx, keyFn, "")
|
||||
}
|
||||
|
||||
// WorkedCountyKeysPred restricts the county ledger to QSOs matching pred.
|
||||
func (r *Repo) WorkedCountyKeysPred(ctx context.Context, keyFn func(state, cnty string) string, pred string) (map[string]struct{}, error) {
|
||||
where := ""
|
||||
if pred != "" {
|
||||
where = " AND " + pred
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT COALESCE(state,''), COALESCE(cnty,'') FROM qso
|
||||
WHERE dxcc IN (291,110,6) AND cnty IS NOT NULL AND cnty != ''`)
|
||||
WHERE dxcc IN (291,110,6) AND cnty IS NOT NULL AND cnty != ''`+where)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2755,9 +2784,18 @@ func (r *Repo) WorkedCountyKeys(ctx context.Context, keyFn func(state, cnty stri
|
||||
// WorkedStateKeys returns the set of US states already worked, uppercased —
|
||||
// the WAS ledger the cluster and decode panels judge NEW STATE against.
|
||||
func (r *Repo) WorkedStateKeys(ctx context.Context) (map[string]struct{}, error) {
|
||||
return r.WorkedStateKeysPred(ctx, "")
|
||||
}
|
||||
|
||||
// WorkedStateKeysPred restricts the state ledger to QSOs matching pred.
|
||||
func (r *Repo) WorkedStateKeysPred(ctx context.Context, pred string) (map[string]struct{}, error) {
|
||||
where := ""
|
||||
if pred != "" {
|
||||
where = " AND " + pred
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT UPPER(COALESCE(state,'')) FROM qso
|
||||
WHERE dxcc IN (291,110,6) AND state IS NOT NULL AND state != ''`)
|
||||
WHERE dxcc IN (291,110,6) AND state IS NOT NULL AND state != ''`+where)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3385,6 +3423,30 @@ type SlotStats struct {
|
||||
// only way those two agree.
|
||||
const ConfirmedValues = "('Y','V')"
|
||||
|
||||
// ConfirmSourcesPredicate builds the SQL that says "this QSO is confirmed",
|
||||
// from the operator's chosen sources — the same choice the award engine's
|
||||
// fixed three (LoTW, card, eQSL) used to hard-code. QRZ.com's confirmation is
|
||||
// its download status; HRDLog has no confirmation field in ADIF at all, which
|
||||
// is why it cannot be offered. Empty input falls back to the classic three.
|
||||
func ConfirmSourcesPredicate(sources []string) string {
|
||||
cols := map[string]string{
|
||||
"lotw": "lotw_rcvd",
|
||||
"card": "qsl_rcvd",
|
||||
"eqsl": "eqsl_rcvd",
|
||||
"qrz": "qrzcom_qso_download_status",
|
||||
}
|
||||
var parts []string
|
||||
for _, src := range sources {
|
||||
if col, ok := cols[strings.ToLower(strings.TrimSpace(src))]; ok {
|
||||
parts = append(parts, col+" IN "+ConfirmedValues)
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "(lotw_rcvd IN " + ConfirmedValues + " OR qsl_rcvd IN " + ConfirmedValues + " OR eqsl_rcvd IN " + ConfirmedValues + ")"
|
||||
}
|
||||
return "(" + strings.Join(parts, " OR ") + ")"
|
||||
}
|
||||
|
||||
// GetSlotStats computes the worked/confirmed slot and DXCC tallies in one pass.
|
||||
// "Confirmed" = LoTW or paper QSL received (the award-valid sources).
|
||||
func (r *Repo) GetSlotStats(ctx context.Context) (SlotStats, error) {
|
||||
|
||||
Reference in New Issue
Block a user