refactor(aprs): mix calibrate.aprs_144 surfaces silent drops + guards
- Print '<computed> / <attempted>' for both positive (HRRR-coverage) and negative (random-baseline → HRRR) factor compute stages so an operator can see how much of the corpus survived each filter. - Guard at task entry: Mix.raise if BandConfig has no 144 MHz entry. Without it the Recalibrator silently falls back to 10 GHz physics. - random_baseline now draws from sample_size: max(n, 5_000) so small-n fits don't cluster geographically. - Wrap body in try/after Oban.resume_all_queues so iex -S mix workflows don't leave Oban paused after the task returns.
This commit is contained in:
parent
7a8623aed8
commit
81e3a54a97
1 changed files with 44 additions and 14 deletions
|
|
@ -42,8 +42,24 @@ defmodule Mix.Tasks.Calibrate.Aprs144 do
|
|||
@impl Mix.Task
|
||||
def run(argv) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
if BandConfig.get(@band_mhz) == nil do
|
||||
Mix.raise("BandConfig has no #{@band_mhz} MHz entry; cannot calibrate.")
|
||||
end
|
||||
|
||||
_ = Oban.pause_all_queues(Oban)
|
||||
|
||||
try do
|
||||
do_run(argv)
|
||||
after
|
||||
# Allow `iex -S mix` workflows to keep using Oban after the task
|
||||
# returns; for one-shot Mix invocations the BEAM exits and this is
|
||||
# a no-op.
|
||||
_ = Oban.resume_all_queues(Oban)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_run(argv) do
|
||||
{opts, _, _} =
|
||||
OptionParser.parse(argv,
|
||||
switches: [
|
||||
|
|
@ -60,10 +76,10 @@ defmodule Mix.Tasks.Calibrate.Aprs144 do
|
|||
max_packets = Keyword.get(opts, :max_packets, 50_000)
|
||||
|
||||
Mix.shell().info("APRS-144 calibration")
|
||||
Mix.shell().info(" since_hours: #{since_hours}")
|
||||
Mix.shell().info(" epochs: #{epochs}")
|
||||
Mix.shell().info(" since_hours: #{since_hours}")
|
||||
Mix.shell().info(" epochs: #{epochs}")
|
||||
Mix.shell().info(" learning_rate: #{learning_rate}")
|
||||
Mix.shell().info(" max_packets: #{max_packets}")
|
||||
Mix.shell().info(" max_packets: #{max_packets}")
|
||||
Mix.shell().info("")
|
||||
|
||||
since = DateTime.add(DateTime.utc_now(), -since_hours * 3600, :second)
|
||||
|
|
@ -79,14 +95,22 @@ defmodule Mix.Tasks.Calibrate.Aprs144 do
|
|||
Mix.shell().info("Parsed #{length(hops)} verified RF hops")
|
||||
|
||||
positives = compute_positive_factors(hops)
|
||||
Mix.shell().info("Computed #{length(positives)} positive factor vectors with HRRR coverage")
|
||||
|
||||
Mix.shell().info(
|
||||
"Computed #{length(positives)} / #{length(hops)} positive factor vectors " <>
|
||||
"(#{length(hops) - length(positives)} hops dropped for missing HRRR coverage)"
|
||||
)
|
||||
|
||||
if length(positives) < @min_samples do
|
||||
Mix.shell().info("")
|
||||
Mix.shell().info("Refusing to fit: only #{length(positives)} positive samples (need >= #{@min_samples})")
|
||||
else
|
||||
negatives = compute_negative_factors(length(positives))
|
||||
Mix.shell().info("Computed #{length(negatives)} negative factor vectors")
|
||||
{negatives, attempted} = compute_negative_factors(length(positives))
|
||||
|
||||
Mix.shell().info(
|
||||
"Computed #{length(negatives)} / #{attempted} negative factor vectors " <>
|
||||
"(#{attempted - length(negatives)} samples dropped for missing HRRR coverage)"
|
||||
)
|
||||
|
||||
if length(negatives) < @min_samples do
|
||||
Mix.shell().info("")
|
||||
|
|
@ -142,14 +166,20 @@ defmodule Mix.Tasks.Calibrate.Aprs144 do
|
|||
end
|
||||
|
||||
defp compute_negative_factors(n) do
|
||||
n
|
||||
|> Backtest.random_baseline(sample_size: n)
|
||||
|> Enum.flat_map(fn {lat, lon, time} ->
|
||||
case Weather.find_nearest_hrrr(lat, lon, time) do
|
||||
nil -> []
|
||||
profile -> [Recalibrator.compute_factors(profile, time, @band_mhz)]
|
||||
end
|
||||
end)
|
||||
# Use a wide contact-pool draw (5_000 minimum) so the random baseline
|
||||
# samples don't cluster geographically when n is small. Backtest's
|
||||
# default :sample_size is 5_000.
|
||||
baselines = Backtest.random_baseline(n, sample_size: max(n, 5_000))
|
||||
|
||||
factors =
|
||||
Enum.flat_map(baselines, fn {lat, lon, time} ->
|
||||
case Weather.find_nearest_hrrr(lat, lon, time) do
|
||||
nil -> []
|
||||
profile -> [Recalibrator.compute_factors(profile, time, @band_mhz)]
|
||||
end
|
||||
end)
|
||||
|
||||
{factors, length(baselines)}
|
||||
end
|
||||
|
||||
defp print_results(result, positives, negatives) do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue