Never call a string of Morse tones "words"

A repeater identifying itself in CW over an FM carrier came back from the
recogniser as "2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0" -- one digit per tone,
sixteen characters of nothing, which cleared the five-character bar and
cost the capture its waterfall.

The rule now lives in one place, waterfall.is_readable, shared by the
scanner and the waterfall command: voice, no Morse, and more than a
handful of characters.

  bandsaunter waterfall --check-morse

runs the CW decoder over the recordings a sidecar calls readable, for
sidecars written before the decoder could hear an ident over an FM
carrier, and draws -- and records the ident in -- the ones that have one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
The Dust Council 2026-09-03 21:43:51 -07:00
parent dee262e130
commit a8a8548369
9 changed files with 190 additions and 29 deletions

View file

@ -376,3 +376,91 @@ def test_the_command_notes_the_picture_in_the_sidecar(tmp_path, monkeypatch):
assert _waterfall_command(str(tmp_path)) == 0
hit = json.loads((tmp_path / "0146.940000MHz--b-ook.json").read_text())["hit"]
assert Path(hit["waterfall_path"]).is_file()
# -- Morse is never words ----------------------------------------------------
def test_a_transcript_of_morse_tones_is_not_words():
"""Whisper renders a CW ident as one digit per tone: sixteen characters
of nothing, which clears the bar and means nothing."""
hit = {"category": "voice", "morse_complete": "KSQ330"}
assert not wf.is_readable(hit, "2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0", 5)
def test_words_over_the_top_of_an_ident_are_still_words():
assert wf.is_readable({"category": "voice"},
"net control this is W1AW standing by", 5)
def test_the_partial_morse_text_counts_as_much_as_the_complete_one():
"""A keyed carrier whose timing only half resolved is still a keyed
carrier, and the transcript of it is still digits."""
hit = {"category": "voice", "morse_text": "K SQ 3 30"}
assert not wf.is_readable(hit, "3-3-0-5-9-7-0-8", 5)
def test_a_capture_that_identified_itself_in_morse_is_drawn(tmp_path,
recogniser,
monkeypatch):
"""The whole point: an FM carrier with a CW ident on the end of it gets
a picture, however long a string of digits the recogniser made of it."""
from bandsaunter.scanner import Scanner
class _Ident:
is_morse = True
text = "KSQ330"
complete_text = "KSQ330"
wpm = 20.0
confidence = 0.9
monkeypatch.setattr(Scanner, "_morse_from_recording",
lambda self, rec, morse: _Ident())
recogniser["text"] = "2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0"
scanner = _scan(tmp_path, recogniser["text"])
assert scanner.stats.recordings >= 1
assert len(drawings(tmp_path)) == scanner.stats.recordings
for meta in tmp_path.glob("*.json"):
if meta.name.startswith("scan_log"):
continue
assert json.loads(meta.read_text())["hit"]["morse_complete"] == "KSQ330"
def test_the_command_draws_an_ident_it_finds_in_the_sidecar(tmp_path,
monkeypatch):
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
_recording(tmp_path, "0154.369068MHz--a-nfm", tone(1200.0),
hit={"category": "voice", "frequency": 154.369068e6,
"mode": "nfm", "morse_complete": "KSQ330"},
transcript="2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0")
assert _waterfall_command(str(tmp_path)) == 0
assert len(drawings(tmp_path)) == 1
def test_check_morse_listens_to_what_the_sidecar_calls_readable(tmp_path,
monkeypatch):
"""A sidecar written before the CW decoder could hear an ident over an FM
carrier calls a repeater readable. Listening again finds it."""
from morse_gen import morse_audio
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
ident = morse_audio("DE KSQ330 KSQ330", wpm=20, fs=FS, snr_db=25)
_recording(tmp_path, "0154.369068MHz--a-nfm", ident,
hit={"category": "voice", "frequency": 154.369068e6,
"mode": "nfm", "morse_text": "", "morse_complete": ""},
transcript="2-2-2-3-3-5-2-7-0-5-9-7-0-8-1-0")
assert _waterfall_command(str(tmp_path)) == 0
assert drawings(tmp_path) == [] # the sidecar was believed
assert _waterfall_command(str(tmp_path), "--check-morse") == 0
assert len(drawings(tmp_path)) == 1
hit = json.loads((tmp_path / "0154.369068MHz--a-nfm.json").read_text())["hit"]
assert "KSQ330" in hit["morse_complete"]
def test_check_morse_leaves_a_conversation_alone(tmp_path, monkeypatch):
"""It has to be able to say no, or it is just --all with a long wait."""
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
_recording(tmp_path, "0146.520000MHz--a-nfm", tone(1200.0),
hit={"category": "voice", "frequency": 146.52e6},
transcript="net control this is W1AW standing by")
assert _waterfall_command(str(tmp_path), "--check-morse") == 0
assert drawings(tmp_path) == []