QSL_VIA is the manager. QSL_SENT_VIA and QSL_RCVD_VIA are the ADIF "QSL Via"
enumeration — B bureau, D direct, E electronic, M manager (import-only) —
and say how a card travelled. OpsLog had one column for all three:
- the import folded QSL_SENT_VIA into QSL_VIA whenever QSL_VIA was empty,
which is exactly a Log4OM export (it defaults QSL_SENT_VIA to E), so
OE6CLD saw "E" everywhere OpsLog shows the manager;
- QSL_RCVD_VIA was listed in adifPromoted with no column behind it, so it
was not stored, not kept among the extras, and not exported — dropped
outright on import;
- neither was ever written on export, so an import followed by an export
destroyed both;
- and OpsLog polluted the field itself: the QSL Manager panel wrote
"Bureau" / "Direct" / "Electronic", in full words, into QSL_VIA.
Two columns added (migration 0027), carried through the five places a
promoted ADIF field has to touch, with round-trip tests pinning the reported
case. The QSL panel now offers Bureau / Direct / Electronic for each
direction and stores the enumeration; the manager field is labelled as the
manager and holds only that. M is kept when a file gives it and never
written back out.
Existing logs hold a mixture of the two in one column. The repair is offered,
not performed: the count is shown once per log with a plain question, and a
"no" is remembered. It moves only where QSL_SENT_VIA is still empty, and only
values that normalise to the enumeration — a manager is a callsign and can
never be one of those six words, which a test pins against real manager calls.
ADIF defines CNTY as "STATE,COUNTY" — "GA,BARROW". OpsLog wrote the bare county
name, which a receiving logger cannot resolve: county names repeat across
states, and there is a Washington County in thirty of them. The award engine
here was never affected, since it reads the columns rather than the ADIF; the
damage was to every file we hand to someone else.
One writer covers everything — writeRecord — so this fixes file exports and the
LoTW, Club Log, HRDLog and QRZ uploads together. MY_CNTY gets the same
treatment.
The county alone is still written when no state is known: a bare name is worth
more than nothing, and inventing a prefix would be worse than either. A value
that already carries a comma passes through untouched, so re-exporting an
imported record cannot double the prefix.
Import is the mirror: "GA,BARROW" fills both columns, and a file carrying the
bare county — as OpsLog's own older exports do — still imports unchanged. The
state parsed out of CNTY only fills a blank STATE, never overrides it: STATE is
the dedicated field and the more specific statement.
An operator worked the RSGB IOTA contest in another logger and got back records
carrying <STATE:5>EU005: N1MM-class software stores the received exchange in the
column its contest module uses, not the one ADIF reserves. Imported verbatim,
every QSO gains a US-state field reading "EU005" and the IOTA award sees
nothing — the reference is in the log, just not where anything looks for it.
The import dialog gains optional source → destination rows, prefilled with
STATE → IOTA since that is the case that prompted it. Applied to the RAW record
before conversion, so it works for promoted columns and Extras alike and the
destination is parsed exactly as if the file had carried it there.
Two rules, both from the same principle that the file outranks our guess:
- the source is CLEARED, so a reference does not linger in STATE where it
would show as the contacted station's US state in the grid and every export;
- a destination the file already filled is kept, judged against the record as
it ARRIVED — otherwise the result would depend on Go's map iteration order,
which is deliberately random.
The swap case in the test is what forced that second rule to be stated: "never
overwrite" and "exchange two fields" cannot both hold silently, so a destination
that is itself a mapping source is treated as an explicit swap and nothing else
is overwritten.
Backend (Go 1.25 / Wails v2):
- QSO storage on SQLite (modernc) with embedded migrations (0001..0005)
- Streaming ADIF import (batch insert) + WorkedBefore per callsign and DXCC
- Callsign lookup with QRZ.com + HamQTH providers (primary/failsafe routing)
and SQLite-backed TTL cache
- DXCC resolver from cty.dat (auto-download, longest-prefix-match)
- Multi-profile operator identities (home/portable/SOTA/contest) — every
QSO stamps MY_* from the active profile
- CAT control via OmniRig COM on a single OS-locked goroutine, with
bidirectional sync (freq/mode/band/split/VFOs) and Rig1/Rig2 hot-swap
- Settings store (key/value), CAT debug log at %APPDATA%/HamLog/cat.log
Frontend (React 18 + TypeScript + Tailwind v4 + shadcn-style):
- Single-row entry strip with CAT-aware band/mode/freq, RST, Start/End
UTC, per-field locks (band/mode/freq/start/end) for backdated QSOs
- Topbar: live freq (MHz.kHz.Hz dotted), live UTC, band/mode/SPLIT badges,
CAT pill with rig selector and clickable Azimuth pill (rotor TODO)
- Settings tree: Profiles (Log4OM-style manager), Station Information
(edits the active profile), unified Callsign Lookup with Test buttons,
Bands/Modes lists, CAT
- Worked-before matrix (band × mode × class) with new-DXCC highlighting
- ADIF import from menu + Maintenance > Refresh cty.dat
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>