prop/test/mix/tasks/calibrate_aprs_144_test.exs
Graham McIntire 7a8623aed8
feat(aprs): mix calibrate.aprs_144 task for fitting 144 MHz weights
Pulls verified RF hops from aprs.me's 24h packet retention, computes
factor vectors at each hop's midpoint via the existing Recalibrator,
and runs gradient descent against random-baseline negatives.

Dry-run: prints proposed weights but does NOT mutate band_config.ex.
Refuses to fit on <50 positive or negative samples.
2026-05-01 12:57:41 -05:00

49 lines
1.5 KiB
Elixir

defmodule Mix.Tasks.Calibrate.Aprs144Test do
@moduledoc """
Smoke test for the APRS-144 calibration mix task. The task is mostly
orchestration: Aprs query + PathParser + Recalibrator. Each piece has
its own unit tests, so this only confirms the wiring holds together
and that the empty-corpus path exits via the "refusing to fit" branch
rather than crashing.
"""
use Microwaveprop.DataCase, async: false
alias Ecto.Adapters.SQL.Sandbox
alias Microwaveprop.AprsRepo
alias Mix.Tasks.Calibrate.Aprs144
setup do
pid = Sandbox.start_owner!(AprsRepo, shared: true)
on_exit(fn -> Sandbox.stop_owner(pid) end)
original_shell = Mix.shell()
Mix.shell(Mix.Shell.Process)
on_exit(fn -> Mix.shell(original_shell) end)
:ok
end
test "empty aprs DB walks the refuse-to-fit branch without raising" do
output =
ExUnit.CaptureIO.capture_io(fn ->
# Tiny knobs so the task is fast even if it ever did reach training.
Aprs144.run(["--since-hours", "1", "--epochs", "1", "--lr", "0.1", "--max-packets", "10"])
end)
# Mix.shell() messages land in the test mailbox; collect them all.
messages = collect_mix_messages()
combined = Enum.join(messages, "\n") <> "\n" <> output
assert combined =~ "APRS-144 calibration"
assert combined =~ "Pulled 0 packets"
assert combined =~ "Refusing to fit"
end
defp collect_mix_messages(acc \\ []) do
receive do
{:mix_shell, :info, [msg]} -> collect_mix_messages([msg | acc])
after
0 -> Enum.reverse(acc)
end
end
end