fix: QRZ.com download marked unconfirmed QSOs as confirmed
The operator's own fetch settles it. One record, both fields: <app_qrzlog_status:1>N <qrzcom_qso_download_status:1>Y QRZ says the QSO is NOT confirmed and sets the download field anyway — it marks what was handed back, not what was confirmed. OpsLog read it as a confirmation, so eighteen QSOs came back "UPDATED" and green, none of them confirmed on QRZ. The site's own logbook page shows them without a star. Only app_qrzlog_status = C counts now. The test that asserted otherwise is corrected and carries the real record. And the download now takes back what it wrongly gave: when QRZ explicitly says not-confirmed and our log says confirmed, the flag is cleared and reported on its own line. These QSOs count towards award slots, so leaving them green until someone noticed was not an option — and nothing else in the app would ever have undone them. Only on an explicit no: a record with no app_qrzlog_status is QRZ saying nothing, and silence is not a retraction.
This commit is contained in:
@@ -10132,6 +10132,7 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
|
||||
mIdx, _ := a.qso.BuildMatchIndex(ctx, qrzOwner)
|
||||
const qrzMatchWindow = 10 * time.Minute
|
||||
qrzSkippedOtherCall := 0
|
||||
unconfirmed := 0 // QSOs whose wrongly-set QRZ flag was taken back
|
||||
// QRZ confirmations are QRZ-specific (not award-valid), so NEW is
|
||||
// judged only against other QRZ confirmations.
|
||||
sets, _ := a.qso.ConfirmedSlots(ctx, []string{"qrzcom_qso_download_status"})
|
||||
@@ -10158,6 +10159,25 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
|
||||
allKeys[k] = true
|
||||
}
|
||||
if !qrzRecordConfirmed(rec) {
|
||||
// QRZ says this one is NOT confirmed. If our log says it is, that
|
||||
// is a mark OpsLog set wrongly before it learned which field
|
||||
// carries QRZ's answer — take it back rather than leave a QSO
|
||||
// counting towards an award it has not earned.
|
||||
//
|
||||
// Only on an EXPLICIT no: a record with no app_qrzlog_status at
|
||||
// all is QRZ saying nothing, and silence is not a retraction.
|
||||
if st := strings.TrimSpace(rec["app_qrzlog_status"]); st != "" {
|
||||
if q, ok := adif.RecordToQSO(rec); ok && q.Callsign != "" {
|
||||
if id, found := mIdx.Match(q.Callsign, q.Band, q.Mode, q.QSODate.UTC(), qrzMatchWindow); found && alreadyQrz[id] {
|
||||
if e := a.qso.ClearQRZConfirmed(ctx, id); e == nil {
|
||||
delete(alreadyQrz, id)
|
||||
unconfirmed++
|
||||
emit(fmt.Sprintf("Callsign: %s Date: %s Band: %s Mode: %s ### NOT CONFIRMED ON QRZ — flag cleared ###",
|
||||
q.Callsign, q.QSODate.UTC().Format("2006-01-02 15:04"), q.Band, q.Mode))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
q, ok := adif.RecordToQSO(rec)
|
||||
@@ -10252,6 +10272,11 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
|
||||
sort.Strings(keys)
|
||||
emit(fmt.Sprintf("Parsed %d record(s). Fields seen: %s", parsed, strings.Join(keys, ", ")))
|
||||
emit(fmt.Sprintf("Confirmed %d, added %d (of %d returned)", matched, added, total))
|
||||
if unconfirmed > 0 {
|
||||
// Worth its own line and not a footnote: these QSOs were showing as
|
||||
// confirmed a moment ago, and an award total may move because of it.
|
||||
emit(fmt.Sprintf("Cleared %d QSO(s) that QRZ.com does NOT confirm — they had been marked wrongly by an earlier version", unconfirmed))
|
||||
}
|
||||
if perr != nil {
|
||||
emit("The download was INCOMPLETE — QRZ.com cut the ADIF short, so the records after that point were not read.")
|
||||
emit("The last-download date has NOT been moved, so nothing is lost: run it again (a narrower window returns less data).")
|
||||
@@ -10406,18 +10431,20 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
|
||||
// "they all turn to Y" (2026-07-29), and on a log full of paper QSLs that is
|
||||
// most of it.
|
||||
//
|
||||
// A QRZ confirmation is QRZ's own statement, and only two fields carry it:
|
||||
// ONE field carries it: app_qrzlog_status, where C means confirmed.
|
||||
//
|
||||
// app_qrzlog_status = C QRZ's confirmed marker on the record
|
||||
// qrzcom_qso_download_status = Y the ADIF field QRZ sets for it
|
||||
// qrzcom_qso_download_status was accepted too, and that was wrong. It is not a
|
||||
// confirmation: QRZ sets it to Y on everything it hands back. An operator's own
|
||||
// fetch showed both fields on the same record —
|
||||
//
|
||||
// Anything else is a claim from some other source and must not be read as a QRZ
|
||||
// confirmation — this status feeds award slots, so a false Y is a QSO counted as
|
||||
// confirmed when it is not.
|
||||
// <app_qrzlog_status:1>N <qrzcom_qso_download_status:1>Y
|
||||
//
|
||||
// — a QSO QRZ says is NOT confirmed, which OpsLog then marked confirmed. That
|
||||
// is how a whole download turned green (2026-07-31).
|
||||
//
|
||||
// This status feeds award slots, so a false Y is a QSO counted as confirmed when
|
||||
// it is not — the one error worth being strict about here.
|
||||
func qrzRecordConfirmed(rec adif.Record) bool {
|
||||
if strings.EqualFold(rec["qrzcom_qso_download_status"], "Y") {
|
||||
return true
|
||||
}
|
||||
return strings.EqualFold(strings.TrimSpace(rec["app_qrzlog_status"]), "C")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user