External services (QRZ/Clublog/LoTW) + QSL Manager
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1062,6 +1062,84 @@ func DedupeKey(callsign, qsoDateMinute, band, mode string) string {
|
||||
return strings.ToUpper(callsign) + "|" + qsoDateMinute + "|" + strings.ToLower(band) + "|" + strings.ToUpper(mode)
|
||||
}
|
||||
|
||||
// DedupeKeyIDs returns a map of dedupe key → QSO id, for matching downloaded
|
||||
// confirmations back to local QSOs.
|
||||
func (r *Repo) DedupeKeyIDs(ctx context.Context) (map[string]int64, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT id, callsign, strftime('%Y-%m-%dT%H:%M', qso_date), band, mode
|
||||
FROM qso`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make(map[string]int64, 1024)
|
||||
for rows.Next() {
|
||||
var id int64
|
||||
var call, when, band, mode string
|
||||
if err := rows.Scan(&id, &call, &when, &band, &mode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[DedupeKey(call, when, band, mode)] = id
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ConfirmedSets captures which DXCC / band / slot combinations are already
|
||||
// confirmed (by any QSL system), so a freshly-downloaded confirmation can be
|
||||
// flagged as a NEW DXCC / NEW BAND / NEW SLOT.
|
||||
type ConfirmedSets struct {
|
||||
DXCC map[int]bool // dxcc entity confirmed
|
||||
Band map[string]bool // "dxcc|band"
|
||||
Slot map[string]bool // "dxcc|band|mode"
|
||||
}
|
||||
|
||||
// SlotKey / BandKey build the composite keys used in ConfirmedSets.
|
||||
func BandKey(dxcc int, band string) string { return fmt.Sprintf("%d|%s", dxcc, strings.ToLower(band)) }
|
||||
func SlotKey(dxcc int, band, mode string) string {
|
||||
return fmt.Sprintf("%d|%s|%s", dxcc, strings.ToLower(band), strings.ToUpper(mode))
|
||||
}
|
||||
|
||||
// ConfirmedSlots returns the set of confirmed DXCC/band/slot combos. A QSO
|
||||
// counts as confirmed when any received flag (LoTW, paper, eQSL) is "Y".
|
||||
func (r *Repo) ConfirmedSlots(ctx context.Context) (ConfirmedSets, error) {
|
||||
sets := ConfirmedSets{DXCC: map[int]bool{}, Band: map[string]bool{}, Slot: map[string]bool{}}
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT COALESCE(dxcc,0), LOWER(COALESCE(band,'')), UPPER(COALESCE(mode,''))
|
||||
FROM qso
|
||||
WHERE lotw_rcvd = 'Y' OR qsl_rcvd = 'Y' OR eqsl_rcvd = 'Y'`)
|
||||
if err != nil {
|
||||
return sets, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var dxcc int
|
||||
var band, mode string
|
||||
if err := rows.Scan(&dxcc, &band, &mode); err != nil {
|
||||
return sets, err
|
||||
}
|
||||
if dxcc == 0 {
|
||||
continue
|
||||
}
|
||||
sets.DXCC[dxcc] = true
|
||||
sets.Band[BandKey(dxcc, band)] = true
|
||||
sets.Slot[SlotKey(dxcc, band, mode)] = true
|
||||
}
|
||||
return sets, rows.Err()
|
||||
}
|
||||
|
||||
// MarkLoTWConfirmed stamps LOTW_QSL_RCVD=Y and the received date on a QSO
|
||||
// after a LoTW confirmation download. date is an ADIF YYYYMMDD string.
|
||||
func (r *Repo) MarkLoTWConfirmed(ctx context.Context, id int64, date string) error {
|
||||
_, err := r.db.ExecContext(ctx,
|
||||
`UPDATE qso SET lotw_rcvd = 'Y', lotw_rcvd_date = ?,
|
||||
updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?`,
|
||||
date, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mark lotw confirmed %d: %w", id, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// scanner is what both *sql.Row and *sql.Rows satisfy for our needs.
|
||||
type scanner interface {
|
||||
Scan(dest ...any) error
|
||||
|
||||
Reference in New Issue
Block a user