Detect callsigns in transcripts, and say whose they are
Under the transcript, headed DETECTED CALLSIGNS:, every callsign heard in it with the name and location on its licence. Finding them is not one regular expression over the text as written. A speech recogniser is poor at callsigns -- they are not words, they are said one character at a time -- so it breaks them wherever the speaker paused and writes the phonetic alphabet down verbatim. The recording that prompted this has "Alright, KU 0W" in it, with a space; spelled out it would have been "kilo uniform zero whiskey". All three forms read back to KU0W. Not inventing them matters more. A run of words is accepted only when none of its parts is an ordinary English word: "or 3. Can you open 4" and "CC1 boy", both from real transcripts here, fit the shape once the punctuation is gone and are not callsigns. A single token said in one breath is still trusted, because W1BOY is a perfectly good callsign, and a lone "a" or "i" cannot start a join or "a B4U player" becomes AB4U. Across the 126 transcripts in the recordings directory that turns three candidates into the one that was actually said. Lookups use the FCC's own licence data at callook.info -- no account, no key, the callsign the only thing sent. They never delay the display: the entry reads "looking up" and fills itself in, and results are cached under ~/.cache so a net recorded night after night is looked up once. --no-lookup contacts nothing and still describes a callsign from its own structure, the ITU prefix giving the country and the digit the US district, which is also all there is to say for callsigns outside the US. --callsigns prints everyone who identified themselves and where they were heard. Also asked: are transcripts appended to, or overwritten, when another transmission arrives on the same frequency? Neither could be shown from reading the code alone, so there are now three tests that run real scans and look at the files. By default each transmission has a transcript of its own -- the timestamp is in the name, so two overs cannot land on one file. With --combine there is one recording per frequency and therefore one transcript, opened for append with the time of each over; a second scan into the same directory adds to it rather than starting it over, which is the case the last of the three tests covers. The browser and callsign tests refuse to reach the network at all. One test did, quietly, and passed -- visible only because the assertion it failed printed a real operator's address. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
parent
cc317914e1
commit
739a2faaf4
11 changed files with 1502 additions and 16 deletions
|
|
@ -5,6 +5,7 @@ from datetime import datetime
|
|||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import re
|
||||
import pytest
|
||||
|
||||
from bandsaunter import transcribe as tr
|
||||
|
|
@ -424,3 +425,69 @@ def test_a_missing_vendor_directory_is_not_added(tmp_path, monkeypatch):
|
|||
monkeypatch.setattr(tr, "VENDOR_DIR", tmp_path / "absent")
|
||||
tr._add_vendor_path()
|
||||
assert str(tmp_path / "absent") not in sys.path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Two transmissions on one frequency
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_each_transmission_gets_a_transcript_of_its_own(tmp_path, fake_engine,
|
||||
monkeypatch):
|
||||
"""The default is one file per transmission, named after it. Nothing is
|
||||
overwritten because nothing is shared: the timestamp is in the name, so
|
||||
two overs on one frequency cannot land on one file."""
|
||||
monkeypatch.setattr("bandsaunter.scanner.available_engine", lambda: "fake")
|
||||
scanner, hits = _scan(tmp_path, [V(146_520_000, "nfm", 0.4, 12_500, "v")],
|
||||
record_seconds=2.0, max_cycles=3,
|
||||
revisit_seconds=0.05)
|
||||
assert len(hits) >= 2, "only one transmission was captured"
|
||||
texts = sorted(tmp_path.glob("*_transcription.txt"))
|
||||
assert len(texts) == len(hits), [p.name for p in texts]
|
||||
for path in texts:
|
||||
assert "transcribed text" in path.read_text()
|
||||
# One transmission, one line. More than one would mean two captures
|
||||
# had collided on a single name.
|
||||
assert len(path.read_text().strip().split("\n")) == 1, path.name
|
||||
|
||||
|
||||
def test_combining_appends_every_over_to_one_file(tmp_path, fake_engine,
|
||||
monkeypatch):
|
||||
"""With --combine there is one recording per frequency, so there is one
|
||||
transcript per frequency, and each over is added to the end of it with the
|
||||
time it was heard."""
|
||||
monkeypatch.setattr("bandsaunter.scanner.available_engine", lambda: "fake")
|
||||
scanner, hits = _scan(tmp_path, [V(146_520_000, "nfm", 0.4, 12_500, "v")],
|
||||
combine_by_frequency=True,
|
||||
announce_timestamps=False, record_seconds=2.0,
|
||||
max_cycles=3, revisit_seconds=0.05)
|
||||
assert len(hits) >= 2
|
||||
texts = list(tmp_path.glob("*_transcription.txt"))
|
||||
assert len(texts) == 1, [p.name for p in texts]
|
||||
lines = [ln for ln in texts[0].read_text().strip().split("\n") if ln]
|
||||
assert len(lines) == len(hits)
|
||||
for line in lines:
|
||||
assert re.match(r"^\[\d{4}-\d\d-\d\d \d\d:\d\d:\d\d\] ", line), line
|
||||
|
||||
|
||||
def test_a_later_run_never_truncates_an_earlier_transcript(tmp_path,
|
||||
fake_engine,
|
||||
monkeypatch):
|
||||
"""The question this answers: can a later transmission on the same
|
||||
frequency wipe out an earlier one's words? The combined file is opened
|
||||
for append, and it is the only transcript two captures ever share, so an
|
||||
unattended receiver adds to it night after night rather than starting it
|
||||
over."""
|
||||
monkeypatch.setattr("bandsaunter.scanner.available_engine", lambda: "fake")
|
||||
args = dict(combine_by_frequency=True, announce_timestamps=False,
|
||||
record_seconds=2.0, max_cycles=2, revisit_seconds=0.05)
|
||||
tx = [V(146_520_000, "nfm", 0.4, 12_500, "v")]
|
||||
|
||||
_scan(tmp_path, tx, **args)
|
||||
combined = next(iter(tmp_path.glob("*_transcription.txt")))
|
||||
before = combined.read_text()
|
||||
assert before.strip()
|
||||
|
||||
_scan(tmp_path, tx, **args) # a second run into the same directory
|
||||
after = combined.read_text()
|
||||
assert after.startswith(before), "the earlier transcript was overwritten"
|
||||
assert len(after) > len(before), "the later over was not added"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue