fix: keep the precise locator on digital QSOs; make the manual lookup real
Grid on the UDP path — WSJT-X and MSHV can only send a FOUR-character grid, that is all the FT8 protocol carries. The enrichment rule there is "fill only what is empty", so the coarse JN05 always won and QRZ's JN05JG was thrown away: roughly 100 km of accuracy discarded on every digital QSO. The lookup grid is now taken when it EXTENDS the received square. A lookup that disagrees outright (JN18 against JN05) is not a refinement — the station may be portable and what came over the air is then the better record. Both rules are pinned by tests. Manual fetch in the QSO editor — it read the CACHE, valid for 30 days. An operator who upgraded their QRZ subscription went on getting the thin free-account record and clearing the cached row by hand was the only way out. The button now forces a lookup that bypasses the cache, reaches the provider and REFRESHES the stored row, with a 15 s budget instead of 2 s: a deliberate click must reach the network, where giving up early would fall back to cty.dat and look like nothing happened. The automatic type-ahead lookup keeps the cache, which is where it earns its keep. Same fetch: it used `??`, which only guards against null. Go marshals an unset string as "", so a QRZ record with no grid BLANKED the grid already in the QSO. The lookup still wins — that is the point of asking for it — but an empty result no longer erases a good value.
This commit is contained in:
@@ -86,6 +86,22 @@ func (m *Manager) SetProviders(p ...Provider) {
|
||||
|
||||
// Lookup returns a Result for the callsign. Falls back through providers
|
||||
// when one returns ErrNotFound or fails.
|
||||
// forceKey marks a context as a FORCED (operator-requested) lookup, which
|
||||
// bypasses the cache on the way in and refreshes it on the way out. Carried on
|
||||
// the context rather than as a parameter so every existing caller — and the
|
||||
// Provider interface — stays untouched.
|
||||
type forceKey struct{}
|
||||
|
||||
// WithForce returns a context that makes Lookup skip the cache.
|
||||
func WithForce(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, forceKey{}, true)
|
||||
}
|
||||
|
||||
func isForced(ctx context.Context) bool {
|
||||
v, _ := ctx.Value(forceKey{}).(bool)
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *Manager) Lookup(ctx context.Context, callsign string) (Result, error) {
|
||||
call := strings.ToUpper(strings.TrimSpace(callsign))
|
||||
if call == "" {
|
||||
@@ -97,15 +113,23 @@ func (m *Manager) Lookup(ctx context.Context, callsign string) (Result, error) {
|
||||
dxcc := m.dxcc
|
||||
m.mu.RUnlock()
|
||||
|
||||
if r, ok := m.cache.Get(ctx, call); ok {
|
||||
r.Source = "cache"
|
||||
// Re-assert the authoritative DXCC fields (country/zones/continent)
|
||||
// from cty.dat on every cache hit — cheap (in-memory) and lets a
|
||||
// corrected entity mapping (e.g. Sicily → Italy) heal stale cached
|
||||
// rows without waiting for the TTL to expire.
|
||||
fillFromDXCC(&r, dxcc)
|
||||
normalizeNames(&r)
|
||||
return r, nil
|
||||
// A FORCED lookup skips the cache. The cache is right for the automatic
|
||||
// lookup that fires as you type, but it also freezes a wrong answer for the
|
||||
// whole TTL: an operator who upgraded their QRZ subscription kept getting the
|
||||
// thin free-account record for a month, and clearing the cache by hand was
|
||||
// the only way out. A lookup the operator asked for by clicking is a
|
||||
// deliberate act and must reach the provider.
|
||||
if !isForced(ctx) {
|
||||
if r, ok := m.cache.Get(ctx, call); ok {
|
||||
r.Source = "cache"
|
||||
// Re-assert the authoritative DXCC fields (country/zones/continent)
|
||||
// from cty.dat on every cache hit — cheap (in-memory) and lets a
|
||||
// corrected entity mapping (e.g. Sicily → Italy) heal stale cached
|
||||
// rows without waiting for the TTL to expire.
|
||||
fillFromDXCC(&r, dxcc)
|
||||
normalizeNames(&r)
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
|
||||
+9
-1
@@ -1185,7 +1185,15 @@ var filterableColumns = map[string]bool{
|
||||
"iota": true, "sota_ref": true, "pota_ref": true, "wwff_ref": true, "rig": true, "ant": true,
|
||||
"qsl_sent": true, "qsl_rcvd": true, "qsl_via": true,
|
||||
"lotw_sent": true, "lotw_rcvd": true, "eqsl_sent": true, "eqsl_rcvd": true,
|
||||
"qrzcom_qso_upload_status": true, "clublog_qso_upload_status": true, "hrdlog_qso_upload_status": true,
|
||||
"qrzcom_qso_upload_status": true, "qrzcom_qso_download_status": true,
|
||||
"clublog_qso_upload_status": true, "hrdlog_qso_upload_status": true,
|
||||
// Confirmation DATES. ADIF YYYYMMDD strings, so a plain string comparison is
|
||||
// also chronological — "before 20240101" works with no date parsing.
|
||||
"qsl_sent_date": true, "qsl_rcvd_date": true,
|
||||
"lotw_sent_date": true, "lotw_rcvd_date": true,
|
||||
"eqsl_sent_date": true, "eqsl_rcvd_date": true,
|
||||
"qrzcom_qso_upload_date": true, "qrzcom_qso_download_date": true,
|
||||
"clublog_qso_upload_date": true, "hrdlog_qso_upload_date": true,
|
||||
"contest_id": true, "srx": true, "stx": true,
|
||||
"prop_mode": true, "sat_name": true,
|
||||
"station_callsign": true, "operator": true, "my_grid": true, "my_country": true,
|
||||
|
||||
Reference in New Issue
Block a user