Commit graph

2 commits

Author SHA1 Message Date
The Dust Council
fb2bb3344b Name the band beside every frequency, and map who was heard
Two additions, both about turning a number into something meaningful.

A band column.  Next to every frequency -- on the live display, in the
line-per-hit output, in saunterbrowse's list and details -- is the name of
the band it falls in.  421 MHz is the 70 cm amateur band, and being told
so is quicker than remembering where the edges are.

The names come from the existing preset table, so there is one band plan
to keep right rather than two, but naming is not the job that table was
shaped for: several presets cover any frequency, some of them whole-tuner
sweeps that say nothing.  So the candidates are ranked.  Sweeps and the
"-complete" duplicates are dropped outright.  The narrowest of what is
left wins, because it says the most -- 146.52 MHz comes back as the 2 m
simplex calling channel rather than as the whole 2 m band.  Two exceptions
where the narrowest would be the wrong answer: ISM yields to the
allocation it shares (433.92 is 70 cm first, 915 is 33 cm first), and
shortwave broadcast yields to amateur where the two overlap, because
3.9-4.0 and 7.2-7.3 MHz are Region 1 and 3 broadcast but Region 2 amateur,
and this plan is documented as Region 2.  6 MHz really is 49 m shortwave
and is left alone.

The name is written into each capture's sidecar, so it travels with the
recording and an edit to the plan later cannot rewrite history, and
saunterbrowse searches on it: /70 cm finds the band without anyone having
to remember 420-450 MHz.

A map.  A licence says where its holder is, so a list of callsigns is also
a map.  Callsigns heard during a scan are now looked up as the transcripts
come in, announced on the display, and written to callsigns.kml in the
output directory; saunterbrowse --kml builds the same file from recordings
already on disk, and the two continue one map rather than starting two.

One placemark per station, not one per transmission: the same repeater
heard twenty times in an evening is one operator, and twenty pins on one
rooftop would say less than one.  Each pin carries the callsign, the
licensee, the town, the grid square, and every frequency and time it was
heard on.  The file is read back on open and added to, so later scans
build it up rather than replacing it.

Where a licence has no coordinates the grid square's centre is used and
the placemark says so -- a square is kilometres across where an address is
a street.  A callsign with no licence at all is still recorded, in a
folder that starts switched off, because that a station was heard is worth
keeping even when nothing says where.  A file already there that is not
readable as KML is never overwritten.

Also fixed along the way:

- The hit list's "no signals recorded yet" placeholder was one cell short
  of its row, so it landed in the SNR column and wrapped, making the panel
  taller than the layout had budgeted for and scrolling the display off a
  short terminal.  The identification column can no longer wrap either,
  which is what _hit_capacity has always assumed.

- Licence lookups now record coordinates.  The cache is versioned so that
  entries written before this are asked about again, rather than pinning
  every station to its grid square for good.

- CallsignBook.wait dropped joined threads; an all-night scan calls it
  after every transcript and the list only ever grew.

- Tests redirect XDG_CACHE_HOME, so a run no longer reads or writes the
  real lookup cache.

676 -> 761 tests.
2026-08-28 11:02:56 -07:00
The Dust Council
739a2faaf4 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
2026-08-22 15:41:02 -07:00