feat(adif): carry NOTES / COMMENT into contact notes

ADIF records frequently include operator commentary in the
<NOTES> field (multi-line, detailed) or <COMMENT> (short
one-liner). Now that contacts have a notes column, pass that
value through the import pipeline instead of dropping it.

Preference is NOTES over COMMENT to match the ADIF 3.1.4 spec's
intended split — NOTES is the richer field. Whitespace-only
values collapse to nil so a program that always emits a blank
NOTES tag doesn't fill every row with an empty string.

Tests cover NOTES capture, COMMENT fallback, NOTES-over-COMMENT
preference, and the absent-field nil case. ADIF upload blurb
lists the new carry-over so users see it before confirming.
This commit is contained in:
Graham McIntire 2026-04-23 11:05:39 -05:00
parent cd3950f366
commit b2ff27945d
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
3 changed files with 62 additions and 1 deletions

View file

@ -213,6 +213,12 @@ defmodule Microwaveprop.Radio.AdifImport do
raw when is_binary(raw) -> raw |> String.trim() |> String.upcase()
end
# Operator commentary: prefer ADIF <NOTES> (multi-line detailed
# field) over <COMMENT> (short one-liner). Whitespace-only values
# collapse to nil so the column reflects "no notes" instead of an
# empty string — mirrors the normalizer on the submission changeset.
notes = adif_notes(fields)
case {band, timestamp} do
{nil, _} ->
{:invalid, %{row_num: row_num, messages: ["could not determine a valid microwave band"]}}
@ -230,7 +236,8 @@ defmodule Microwaveprop.Radio.AdifImport do
"mode" => mode,
"qso_timestamp" => DateTime.to_iso8601(dt),
"submitter_email" => submitter_email,
"user_declared_prop_mode" => prop_mode
"user_declared_prop_mode" => prop_mode,
"notes" => notes
}
changeset = Contact.submission_changeset(%Contact{}, attrs)
@ -244,6 +251,20 @@ defmodule Microwaveprop.Radio.AdifImport do
end
end
defp adif_notes(fields) do
Enum.find_value([fields["NOTES"], fields["COMMENT"]], fn
nil -> nil
value when is_binary(value) -> non_blank_or_nil(value)
end)
end
defp non_blank_or_nil(value) do
case String.trim(value) do
"" -> nil
_ -> value
end
end
# -- band resolution -------------------------------------------------------
defp resolve_band(fields) do

View file

@ -565,6 +565,10 @@ defmodule MicrowavepropWeb.SubmitLive do
<li>Contacts on amateur bands from 50 MHz and up will be imported</li>
<li>HF contacts (below 50 MHz) are silently skipped</li>
<li>Frequencies are fuzzy-matched to the nearest amateur band</li>
<li>
Operator commentary from <code>NOTES</code>
is carried over (falling back to <code>COMMENT</code>)
</li>
</ul>
</div>
</div>

View file

@ -66,6 +66,42 @@ defmodule Microwaveprop.Radio.AdifImportTest do
assert row.attrs["user_declared_prop_mode"] == nil
end
test "captures ADIF NOTES into notes" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<NOTES:25>Rare Es along TX/OK front<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["notes"] == "Rare Es along TX/OK front"
end
test "falls back to ADIF COMMENT when NOTES is absent" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<COMMENT:10>nice qso!!<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["notes"] == "nice qso!!"
end
test "prefers NOTES over COMMENT when both are present" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<FREQ:9>10368.000<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<NOTES:8>detailed<COMMENT:5>short<EOR>
"""
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["notes"] == "detailed"
end
test "missing NOTES / COMMENT leaves notes nil" do
assert {:ok, preview} = AdifImport.preview(@valid_adif, @submitter)
[row] = preview.valid
assert row.attrs["notes"] == nil
end
test "parses ADIF with BAND field instead of FREQ" do
adif = """
<STATION_CALLSIGN:4>W5XD<CALL:4>K5TR<GRIDSQUARE:4>EM00<MY_GRIDSQUARE:4>EM12<BAND:4>23cm<MODE:2>CW<QSO_DATE:8>20260328<TIME_ON:6>180000<EOR>