fix(dialyzer): clear 125+ warnings under strict flags

Enabled :error_handling, :unknown, :unmatched_returns, :extra_return,
:missing_return in an earlier commit and landed a 129-warning baseline.
Four parallel agents each fixed a directory slice:

- Core contexts (29): Radio, Release, Weather, Beacons, Cache,
  Backtest.Features, Terrain.Srtm, Ionosphere.GiroClient,
  Propagation.RunTiming, Accounts.Scope, RepoListener. Fixes were
  (a) prefix side-effect calls (Task.start, Phoenix.PubSub,
  Logger, :ets.new) with _ = ; (b) tighten/widen specs that didn't
  match actual returns; (c) add missing @type t declarations;
  (d) drop dead parse_int(nil) clause.

- Propagation + weather subdirs (15): FreshnessMonitor, NotifyListener,
  ScoreCache, ScoreCacheReconciler, Weather.FrontalAnalysis,
  Weather.Grib2.Extractor, Weather.Grib2.Wgrib2, GridCache,
  HrrrPointEnqueuer, NexradCache. Same patterns — mostly _ = on
  PubSub / :ets / Repo.insert_all; widened two specs (float ->
  number) where integer returns were reachable.

- Workers (35): BackfillEnqueue, CanadianSoundingFetch,
  ContactImport, ContactWeatherEnqueue, GefsFetch, IemreFetch,
  NarrFetch, SolarIndex, TerrainProfile, WeatherFetch. Prefixed
  Repo.update_all / Radio.set_enrichment_status! / Weather.upsert_*
  side-effect calls. Fixed one :pattern_match in
  CanadianSoundingFetch.most_recent_sounding_time/1 where a
  tautological cond guard generated unreachable code.

- Web + Mix tasks + lib_ml (46 of 50): controllers, LiveViews,
  UserAuth, and 11 mix tasks. Same prefix strategy. 4 remaining
  warnings originate in LiveTable.LiveResource dep macro expansion
  and can't be fixed without forking the dep — added .dialyzer_ignore.exs
  to suppress just those specific file:line pairs.

Also wired ignore_warnings in mix.exs dialyzer config.

mix dialyzer --format short | grep ^lib/ | wc -l -> 0
mix test: 2163 tests, 3 pre-existing flakes, 0 regressions.
This commit is contained in:
Graham McIntire 2026-04-21 10:30:06 -05:00
parent 733a7f5bf1
commit d61fbd346e
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
61 changed files with 354 additions and 309 deletions

10
.dialyzer_ignore.exs Normal file
View file

@ -0,0 +1,10 @@
# LiveTable.LiveResource macro expansion (in the dep) emits
# unmatched_return for Phoenix.PubSub.subscribe/Oban.insert calls
# it generates inside handle_event/3 and maybe_subscribe/1. Can't
# fix without forking the dep; suppress at the `use` line only.
[
{"lib/microwaveprop_web/live/admin/contact_edit_live.ex", :unmatched_return, 4},
{"lib/microwaveprop_web/live/beacon_live/index.ex", :unmatched_return, 4},
{"lib/microwaveprop_web/live/contact_live/index.ex", :unmatched_return, 4},
{"lib/microwaveprop_web/live/user_management_live/index.ex", :unmatched_return, 4}
]

View file

@ -18,6 +18,8 @@ defmodule Microwaveprop.Accounts.Scope do
alias Microwaveprop.Accounts.User
@type t :: %__MODULE__{user: User.t() | nil}
defstruct user: nil
@doc """

View file

@ -46,7 +46,7 @@ defmodule Microwaveprop.Application do
result = Supervisor.start_link(children, opts)
# Run migrations after Repo is started
Microwaveprop.Release.migrate()
_ = Microwaveprop.Release.migrate()
# Warm GridCache from the latest persisted ProfilesFile so /weather
# has data immediately after a pod restart. Runs after the GridCache
@ -56,7 +56,7 @@ defmodule Microwaveprop.Application do
# master: when executed here the loaded rows live on the app master's
# heap and never GC (it's idle after boot), pinning ~800 MiB for the
# lifetime of the pod.
Task.start(fn -> Microwaveprop.Weather.warm_grid_cache_from_latest_profile() end)
{:ok, _pid} = Task.start(fn -> Microwaveprop.Weather.warm_grid_cache_from_latest_profile() end)
# Load ML model in dev/test only (Nx/Axon not compiled for prod)
if Application.get_env(:microwaveprop, :load_ml_model, false) do

View file

@ -269,7 +269,7 @@ defmodule Microwaveprop.Backtest.Features do
it documents the intended API; the backtest will only work once
the pipeline is live.
"""
@spec distance_to_front(float, float, DateTime.t()) :: float | nil
@spec distance_to_front(float, float, DateTime.t()) :: nil
def distance_to_front(_lat, _lon, _valid_time), do: nil
@doc """
@ -278,7 +278,7 @@ defmodule Microwaveprop.Backtest.Features do
bearing, so this is only usable in QSO-level scoring, not grid
scoring. Placeholder until Phase 5.4.
"""
@spec parallel_to_front(float, float, DateTime.t()) :: float | nil
@spec parallel_to_front(float, float, DateTime.t()) :: nil
def parallel_to_front(_lat, _lon, _valid_time), do: nil
@doc "Duct usable for 10 GHz."

View file

@ -72,7 +72,7 @@ defmodule Microwaveprop.Beacons do
end
@doc "Gets a single beacon. Raises if not found."
@spec get_beacon!(Ecto.UUID.t()) :: Beacon.t()
@spec get_beacon!(Ecto.UUID.t()) :: Beacon.t() | nil | [map()]
def get_beacon!(id), do: Beacon |> Repo.get!(id) |> Repo.preload(:user)
@doc """
@ -119,7 +119,7 @@ defmodule Microwaveprop.Beacons do
def delete_beacon(%Beacon{} = beacon) do
case Repo.delete(beacon) do
{:ok, beacon} ->
broadcast({:deleted, beacon})
_ = broadcast({:deleted, beacon})
{:ok, beacon}
other ->
@ -134,7 +134,7 @@ defmodule Microwaveprop.Beacons do
end
defp broadcast_if_ok({:ok, beacon} = result, type) do
broadcast({type, beacon})
_ = broadcast({type, beacon})
result
end

View file

@ -58,6 +58,7 @@ defmodule Microwaveprop.Cache do
@impl true
def init(_opts) do
_ =
:ets.new(@table, [
:set,
:named_table,

View file

@ -172,8 +172,6 @@ defmodule Microwaveprop.Ionosphere.GiroClient do
}
end
defp parse_int(nil), do: nil
defp parse_int(s) do
case Integer.parse(s) do
{n, ""} -> n

View file

@ -32,7 +32,7 @@ defmodule Microwaveprop.Propagation.FreshnessMonitor do
@impl true
def handle_info(:check, state) do
check_freshness()
_ = check_freshness()
Process.send_after(self(), :check, @check_interval)
{:noreply, state}
end

View file

@ -97,7 +97,7 @@ defmodule Microwaveprop.Propagation.NotifyListener do
# unbounded when Rust covers many forecast hours back-to-back.
ScoreCache.prune_older_than(DateTime.add(DateTime.utc_now(), -2, :hour))
Phoenix.PubSub.broadcast(@pubsub, @topic, {:propagation_updated, [valid_time]})
_ = Phoenix.PubSub.broadcast(@pubsub, @topic, {:propagation_updated, [valid_time]})
if err > 0 do
Logger.warning("NotifyListener: warmed #{ok} bands, #{err} failed for valid_time=#{valid_time}")

View file

@ -10,6 +10,8 @@ defmodule Microwaveprop.Propagation.RunTiming do
import Ecto.Changeset
@type t :: %__MODULE__{}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "propagation_run_timings" do

View file

@ -83,7 +83,7 @@ defmodule Microwaveprop.Propagation.ScoreCache do
"""
@spec broadcast_put(non_neg_integer(), DateTime.t(), [score()]) :: :ok
def broadcast_put(band_mhz, valid_time, scores) do
PubSub.broadcast(@pubsub, @topic, {:cache_refresh, band_mhz, valid_time, scores})
_ = PubSub.broadcast(@pubsub, @topic, {:cache_refresh, band_mhz, valid_time, scores})
:ok
end
@ -122,8 +122,8 @@ defmodule Microwaveprop.Propagation.ScoreCache do
@impl true
def init(_opts) do
:ets.new(@table, [:set, :named_table, :public, :compressed, read_concurrency: true])
PubSub.subscribe(@pubsub, @topic)
_ = :ets.new(@table, [:set, :named_table, :public, :compressed, read_concurrency: true])
:ok = PubSub.subscribe(@pubsub, @topic)
{:ok, %{}}
end

View file

@ -56,8 +56,9 @@ defmodule Microwaveprop.Propagation.ScoreCacheReconciler do
def init(opts) do
interval = Keyword.get(opts, :interval_ms, @default_interval_ms)
_ =
if Keyword.get(opts, :run_on_start, true) do
Process.send_after(self(), :sweep, 500)
_ = Process.send_after(self(), :sweep, 500)
end
{:ok, %{interval_ms: interval}}

View file

@ -980,7 +980,7 @@ defmodule Microwaveprop.Radio do
# Apply changes to contact
contact = Repo.get!(Contact, edit.contact_id)
apply_edit_to_contact(contact, edit.proposed_changes)
_ = apply_edit_to_contact(contact, edit.proposed_changes)
Repo.preload(approved, [:user, :contact, :reviewed_by])
end)

View file

@ -21,7 +21,7 @@ defmodule Microwaveprop.Release do
@app :microwaveprop
def migrate do
load_app()
_ = load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
@ -29,30 +29,30 @@ defmodule Microwaveprop.Release do
end
def rollback(repo, version) do
load_app()
_ = load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
def backtest(feature_name) when is_binary(feature_name) do
start_app()
_ = start_app()
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "backtest", feature: feature_name}))
IO.puts("Enqueued backtest for #{feature_name} (job #{job.id})")
end
def backtest_all do
start_app()
_ = start_app()
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "backtest_all"}))
IO.puts("Enqueued consolidated backtest (job #{job.id})")
end
def climatology(min_samples \\ 3) do
start_app()
_ = start_app()
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "climatology", min_samples: min_samples}))
IO.puts("Enqueued climatology build (job #{job.id})")
end
def native_backfill(limit \\ 500) do
start_app()
_ = start_app()
%{rows: rows} =
Microwaveprop.Repo.query!(
@ -88,7 +88,7 @@ defmodule Microwaveprop.Release do
end
def recalibrate do
start_app()
_ = start_app()
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "recalibrate"}))
IO.puts("Enqueued recalibration (job #{job.id})")
end
@ -98,12 +98,12 @@ defmodule Microwaveprop.Release do
# files on disk. Left as a stub that tells the operator what
# happened if they shell into a running release from muscle memory.
def scorer_diff(_new_weights_json) do
start_app()
_ = start_app()
IO.puts("scorer_diff is disabled: propagation_scores table + factors storage have been dropped.")
end
def native_derive(limit \\ 10_000) do
start_app()
_ = start_app()
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "native_derive", limit: limit}))
IO.puts("Enqueued native derive (job #{job.id})")
end
@ -113,7 +113,7 @@ defmodule Microwaveprop.Release do
end
defp load_app do
Application.ensure_all_started(:ssl)
{:ok, _} = Application.ensure_all_started(:ssl)
Application.ensure_loaded(@app)
end

View file

@ -38,6 +38,7 @@ defmodule Microwaveprop.RepoListener do
@impl true
def handle_info({:notification, _pid, _ref, "contact_status_changed", payload}, state) do
_ =
case Jason.decode(payload) do
{:ok, data} ->
Phoenix.PubSub.broadcast(
@ -54,6 +55,7 @@ defmodule Microwaveprop.RepoListener do
end
def handle_info({:notification, _pid, _ref, "oban_job_changed", payload}, state) do
_ =
case Jason.decode(payload) do
{:ok, data} ->
Phoenix.PubSub.broadcast(

View file

@ -93,7 +93,7 @@ defmodule Microwaveprop.Terrain.Srtm do
end
@spec fetch_elevation_profile(float(), float(), float(), float(), String.t(), pos_integer(), keyword()) ::
{:ok, list(map())} | {:error, term()}
{:ok, list(map())}
def fetch_elevation_profile(lat1, lon1, lat2, lon2, tiles_dir, n \\ 64, opts \\ []) do
pts = sample_path(lat1, lon1, lat2, lon2, n)
dist_km = haversine_km(lat1, lon1, lat2, lon2)
@ -130,7 +130,7 @@ defmodule Microwaveprop.Terrain.Srtm do
{:error, :void}
end
:file.close(fd)
_ = :file.close(fd)
result
end

View file

@ -527,12 +527,15 @@ defmodule Microwaveprop.Weather do
end
defp kickoff_async_grid_fill(valid_time) do
_ =
if GridCache.claim_fill(valid_time) do
{:ok, _pid} =
Task.start(fn ->
try do
Logger.info("Weather.grid_cache async fill starting for #{valid_time}")
warm_grid_cache_and_broadcast(valid_time)
_ =
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"weather:updated",

View file

@ -124,7 +124,7 @@ defmodule Microwaveprop.Weather.FrontalAnalysis do
@doc """
Angle between a QSO path and the nearest front (0° = parallel, 90° = crosses).
"""
@spec path_front_angle(float(), float()) :: float()
@spec path_front_angle(float(), float()) :: number()
def path_front_angle(path_bearing_deg, front_bearing_deg) do
diff = abs(path_bearing_deg - front_bearing_deg)
diff = if diff > 180, do: 360 - diff, else: diff

View file

@ -93,7 +93,7 @@ defmodule Microwaveprop.Weather.Grib2.Extractor do
For HRRR scan_mode=64 (j positive, i positive, i consecutive): index = j * nx + i
"""
@spec linear_index({non_neg_integer(), non_neg_integer()}, pos_integer(), non_neg_integer()) ::
non_neg_integer()
number()
def linear_index({i, j}, nx, _scan_mode) do
j * nx + i
end

View file

@ -295,7 +295,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do
{:error, "wgrib2 failed (exit #{exit_code}): #{String.slice(output, 0, 200)}"}
end
after
File.rm(tmp_grib)
_ = File.rm(tmp_grib)
File.rm(tmp_bin)
end
end

View file

@ -69,7 +69,7 @@ defmodule Microwaveprop.Weather.GridCache do
@doc "Insert locally AND broadcast to peer nodes via PubSub."
@spec broadcast_put(DateTime.t(), [row()]) :: :ok
def broadcast_put(valid_time, rows) do
PubSub.broadcast(@pubsub, @topic, {:weather_cache_refresh, valid_time, rows})
_ = PubSub.broadcast(@pubsub, @topic, {:weather_cache_refresh, valid_time, rows})
:ok
end
@ -122,9 +122,9 @@ defmodule Microwaveprop.Weather.GridCache do
@impl true
def init(_opts) do
:ets.new(@table, [:set, :named_table, :public, :compressed, read_concurrency: true])
:ets.new(@lock_table, [:set, :named_table, :protected])
PubSub.subscribe(@pubsub, @topic)
_ = :ets.new(@table, [:set, :named_table, :public, :compressed, read_concurrency: true])
_ = :ets.new(@lock_table, [:set, :named_table, :protected])
:ok = PubSub.subscribe(@pubsub, @topic)
{:ok, %{monitors: %{}}}
end

View file

@ -50,6 +50,7 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do
# || union on round-trip.
id = Ecto.UUID.bingenerate()
_ =
Repo.insert_all(
"hrrr_fetch_tasks",
[

View file

@ -73,7 +73,7 @@ defmodule Microwaveprop.Weather.NexradCache do
@impl true
def init(_opts) do
:ets.new(@table, [:set, :named_table, :public, :compressed, read_concurrency: true])
_ = :ets.new(@table, [:set, :named_table, :public, :compressed, read_concurrency: true])
{:ok, %{}}
end
end

View file

@ -72,6 +72,7 @@ defmodule Microwaveprop.Workers.BackfillEnqueueWorker do
Enum.each(contacts, &ContactWeatherEnqueueWorker.enqueue_for_contact(&1, types))
_ =
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"backfill:enqueue_complete",

View file

@ -61,11 +61,9 @@ defmodule Microwaveprop.Workers.CanadianSoundingFetchWorker do
def most_recent_sounding_time(%DateTime{} = now) do
cutoff = DateTime.add(now, -@publish_delay_seconds, :second)
cond do
cutoff.hour >= 12 ->
if cutoff.hour >= 12 do
%{cutoff | hour: 12, minute: 0, second: 0, microsecond: {0, 0}}
cutoff.hour >= 0 and DateTime.compare(cutoff, %{cutoff | hour: 0, minute: 0, second: 0, microsecond: {0, 0}}) != :lt ->
else
%{cutoff | hour: 0, minute: 0, second: 0, microsecond: {0, 0}}
end
end
@ -91,7 +89,7 @@ defmodule Microwaveprop.Workers.CanadianSoundingFetchWorker do
case UwyoSoundingClient.fetch_sounding(uwyo_code, sounding_time) do
{:ok, [%{profile: profile} | _]} when profile != [] ->
attrs = build_attrs(sounding_time, profile)
Weather.upsert_sounding(station, attrs)
_ = Weather.upsert_sounding(station, attrs)
Logger.info("CanadianSoundings: #{code} ingested sounding with #{length(profile)} levels")

View file

@ -50,7 +50,7 @@ defmodule Microwaveprop.Workers.ContactImportWorker do
updates = build_counter_updates(commit_result, length(chunk))
updated_run = apply_chunk_updates(run.id, updates)
broadcast(run.id, updated_run)
_ = broadcast(run.id, updated_run)
:ok
end

View file

@ -44,6 +44,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
# and UPSERT-union means a backfill tick that adds a new contact in
# an already-scheduled hour collapses into the existing row instead
# of creating a duplicate fetch.
_ =
if :hrrr in types do
{:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])
end
@ -52,7 +53,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
# constraint is honored; Oban OSS insert_all does not check uniqueness.
insert_unique(jobs_by_type[:narr])
mark_enrichment_statuses(contact, types, jobs_by_type)
_ = mark_enrichment_statuses(contact, types, jobs_by_type)
:ok
end
@ -97,6 +98,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
defp mark_enrichment_statuses(contact, types, jobs_by_type) do
ids = [contact.id]
_ =
if contact.pos1 do
mark_pos1_statuses(contact, ids, types, jobs_by_type)
end
@ -113,22 +115,22 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
end
defp mark_missing_path_statuses(ids, types) do
if :terrain in types, do: Radio.set_enrichment_status!(ids, :terrain_status, :unavailable)
if :radar in types, do: Radio.set_enrichment_status!(ids, :radar_status, :unavailable)
_ = if :terrain in types, do: Radio.set_enrichment_status!(ids, :terrain_status, :unavailable)
_ = if :radar in types, do: Radio.set_enrichment_status!(ids, :radar_status, :unavailable)
if :mechanism in types,
do: Radio.set_enrichment_status!(ids, :mechanism_status, :unavailable)
end
defp mark_pos1_statuses(contact, ids, types, jobs_by_type) do
if :weather in types, do: mark_status!(ids, :weather_status, jobs_by_type[:weather])
if :hrrr in types, do: mark_hrrr_status!(contact, ids, jobs_by_type[:hrrr])
_ = if :weather in types, do: mark_status!(ids, :weather_status, jobs_by_type[:weather])
_ = if :hrrr in types, do: mark_hrrr_status!(contact, ids, jobs_by_type[:hrrr])
if :iemre in types, do: mark_status!(ids, :iemre_status, jobs_by_type[:iemre])
end
defp mark_path_statuses(contact, ids, types, jobs_by_type) do
if :terrain in types, do: mark_status!(ids, :terrain_status, jobs_by_type[:terrain])
if :radar in types, do: mark_radar_status!(contact, ids, jobs_by_type[:radar])
_ = if :terrain in types, do: mark_status!(ids, :terrain_status, jobs_by_type[:terrain])
_ = if :radar in types, do: mark_radar_status!(contact, ids, jobs_by_type[:radar])
if :mechanism in types,
do: mark_status!(ids, :mechanism_status, jobs_by_type[:mechanism])
@ -178,7 +180,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
:ok
contacts ->
Radio.backfill_distances(contacts)
_ = Radio.backfill_distances(contacts)
jobs = build_weather_jobs(contacts)
@ -187,7 +189,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
end
ids = Enum.map(contacts, & &1.id)
Radio.mark_weather_queued!(ids)
_ = Radio.mark_weather_queued!(ids)
enqueue_weather_jobs()
end
@ -206,7 +208,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
{:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts(contacts)
ids = Enum.map(contacts, & &1.id)
Radio.mark_hrrr_queued!(ids)
_ = Radio.mark_hrrr_queued!(ids)
enqueue_hrrr_jobs()
end
@ -225,7 +227,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
end
ids = Enum.map(contacts, & &1.id)
Radio.mark_terrain_queued!(ids)
_ = Radio.mark_terrain_queued!(ids)
enqueue_terrain_jobs()
end
@ -350,7 +352,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
end
ids = Enum.map(contacts, & &1.id)
Radio.mark_iemre_queued!(ids)
_ = Radio.mark_iemre_queued!(ids)
enqueue_iemre_jobs()
end

View file

@ -175,6 +175,7 @@ defmodule Microwaveprop.Workers.GefsFetchWorker do
end)
|> Enum.to_list()
_ =
case Propagation.replace_scores(scores, valid_time) do
{:ok, count} ->
Logger.info("GefsFetch: wrote #{count} scores for fh=#{fh} @ #{valid_time}")

View file

@ -30,11 +30,12 @@ defmodule Microwaveprop.Workers.IemreFetchWorker do
case IemClient.fetch_iemre(lat, lon, date) do
{:ok, []} ->
# Store stub so this lat/lon/date isn't retried on future backfills
Weather.upsert_iemre_observation(%{lat: lat, lon: lon, date: date, hourly: []})
_ = Weather.upsert_iemre_observation(%{lat: lat, lon: lon, date: date, hourly: []})
Logger.info("IEMRE: no data available for #{lat},#{lon} @ #{date_str}, stored stub")
:ok
{:ok, data} ->
_ =
Weather.upsert_iemre_observation(%{
lat: lat,
lon: lon,

View file

@ -50,7 +50,7 @@ defmodule Microwaveprop.Workers.NarrFetchWorker do
defp fetch_and_insert(rlat, rlon, valid_time) do
case NarrClient.fetch_profile_at(valid_time, {rlat, rlon}) do
{:ok, attrs} ->
insert_profile(attrs, rlat, rlon, valid_time)
_ = insert_profile(attrs, rlat, rlon, valid_time)
:ok
{:error, reason} = error ->

View file

@ -17,6 +17,7 @@ defmodule Microwaveprop.Workers.SolarIndexWorker do
matched = Enum.filter(all_records, fn r -> r.date == target_date end)
Weather.upsert_solar_indices_batch(matched)
_ =
if matched != [] do
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,

View file

@ -20,7 +20,7 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
def perform(%Oban.Job{args: %{"contact_id" => contact_id}}) do
Microwaveprop.Instrument.span([:worker, :terrain_profile], %{contact_id: contact_id}, fn ->
if Terrain.has_terrain_profile?(contact_id) do
Radio.set_enrichment_status!([contact_id], :terrain_status, :complete)
_ = Radio.set_enrichment_status!([contact_id], :terrain_status, :complete)
:ok
else
analyse_terrain(contact_id)
@ -38,7 +38,7 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2)
else
_ ->
Radio.set_enrichment_status!([contact_id], :terrain_status, :unavailable)
_ = Radio.set_enrichment_status!([contact_id], :terrain_status, :unavailable)
:ok
end
end
@ -65,6 +65,7 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
%{"lat" => p.lat, "lon" => p.lon, "d" => p.d, "elev" => p.elev, "dist_km" => p.dist_km}
end)
_ =
Terrain.upsert_terrain_profile(%{
contact_id: contact_id,
sample_count: length(profile),
@ -77,8 +78,9 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
verdict: analysis.verdict
})
Radio.set_enrichment_status!([contact_id], :terrain_status, :complete)
_ = Radio.set_enrichment_status!([contact_id], :terrain_status, :complete)
_ =
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"contact_enrichment:#{contact_id}",

View file

@ -96,6 +96,7 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do
Logger.info("WeatherFetch ASOS: #{station_code} ingested #{count} observations")
_ =
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"contact_enrichment:weather",
@ -118,7 +119,7 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do
defp fetch_raob(station, station_id, station_code, sounding_time, sounding_time_str, contact_id) do
if Weather.has_sounding?(station.id, sounding_time) do
Logger.debug("WeatherFetch RAOB: #{station_code} already has sounding for #{sounding_time_str}")
broadcast_sounding_fetched(station_id, contact_id)
_ = broadcast_sounding_fetched(station_id, contact_id)
:ok
else
Logger.info("WeatherFetch RAOB: fetching #{station_code} for #{sounding_time_str}")
@ -132,9 +133,9 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do
ingest_raob(station, station_id, station_code, parsed, contact_id)
{:ok, []} ->
Weather.upsert_sounding(station, %{observed_at: sounding_time, profile: [], level_count: 0})
_ = Weather.upsert_sounding(station, %{observed_at: sounding_time, profile: [], level_count: 0})
Logger.info("WeatherFetch RAOB: #{station_code} no data for #{sounding_time_str}, stored stub")
broadcast_sounding_fetched(station_id, contact_id)
_ = broadcast_sounding_fetched(station_id, contact_id)
:ok
{:error, reason} ->
@ -145,15 +146,16 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do
defp ingest_raob(station, station_id, station_code, parsed, contact_id) do
sounding_attrs = build_sounding_attrs(parsed)
Weather.upsert_sounding(station, sounding_attrs)
_ = Weather.upsert_sounding(station, sounding_attrs)
Logger.info("WeatherFetch RAOB: #{station_code} ingested sounding with #{length(parsed.profile)} levels")
broadcast_sounding_fetched(station_id, contact_id)
_ = broadcast_sounding_fetched(station_id, contact_id)
:ok
end
defp broadcast_sounding_fetched(station_id, contact_id) do
_ =
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"contact_enrichment:weather",

View file

@ -5,7 +5,7 @@ defmodule MicrowavepropWeb.HealthController do
def check(conn, _params) do
# Verify DB is reachable
SQL.query!(Microwaveprop.Repo, "SELECT 1")
_ = SQL.query!(Microwaveprop.Repo, "SELECT 1")
conn
|> put_resp_content_type("text/plain")

View file

@ -10,6 +10,7 @@ defmodule MicrowavepropWeb.UserResetPasswordController do
end
def create(conn, %{"user" => %{"email" => email}}) do
_ =
if user = Accounts.get_user_by_email(email) do
{:ok, _email} =
Accounts.deliver_user_reset_password_instructions(

View file

@ -21,6 +21,7 @@ defmodule MicrowavepropWeb.UserSettingsController do
case Accounts.change_user_email(user, user_params) do
%{valid?: true} = changeset ->
_ =
Accounts.deliver_user_update_email_instructions(
Ecto.Changeset.apply_action!(changeset, :insert),
user.email,

View file

@ -73,7 +73,7 @@ defmodule MicrowavepropWeb.Admin.ContactEditLive do
case Radio.approve_edit(edit, admin, note) do
{:ok, approved} ->
EditNotifier.deliver_edit_approved(approved)
_ = EditNotifier.deliver_edit_approved(approved)
{:noreply,
socket
@ -93,7 +93,7 @@ defmodule MicrowavepropWeb.Admin.ContactEditLive do
case Radio.reject_edit(edit, admin, note) do
{:ok, rejected} ->
EditNotifier.deliver_edit_rejected(rejected)
_ = EditNotifier.deliver_edit_rejected(rejected)
{:noreply,
socket

View file

@ -44,7 +44,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
@impl true
def mount(_params, _session, socket) do
if connected?(socket), do: Beacons.subscribe_beacons()
_ = if connected?(socket), do: Beacons.subscribe_beacons()
beacons = Beacons.list_beacons()

View file

@ -159,7 +159,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
@impl true
def mount(%{"id" => id}, _session, socket) do
if connected?(socket), do: Beacons.subscribe_beacons()
_ = if connected?(socket), do: Beacons.subscribe_beacons()
beacon = Beacons.get_beacon!(id)

View file

@ -81,7 +81,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
socket =
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:#{contact.id}")
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:#{contact.id}")
kickoff_hydration(socket, contact)
else
@ -315,7 +315,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
if proposed == %{} do
put_flash(socket, :error, "No changes detected.")
else
Radio.apply_edit_to_contact(contact, proposed)
_ = Radio.apply_edit_to_contact(contact, proposed)
updated = Radio.get_contact!(contact.id)
socket
@ -387,7 +387,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
def handle_async(:solar, {:ok, solar}, socket) do
solar =
if socket.assigns.can_enqueue and is_nil(solar) do
enqueue_missing_solar(socket.assigns.contact)
_ = enqueue_missing_solar(socket.assigns.contact)
nil
else
solar
@ -534,6 +534,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
cond do
has_data ->
_ =
if contact.weather_status != :complete do
Radio.set_enrichment_status!([contact.id], :weather_status, :complete)
end
@ -546,8 +547,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do
if jobs == [] do
%{contact | weather_status: :unavailable}
else
Oban.insert_all(jobs)
Radio.set_enrichment_status!([contact.id], :weather_status, :queued)
_ = Oban.insert_all(jobs)
_ = Radio.set_enrichment_status!([contact.id], :weather_status, :queued)
%{contact | weather_status: :queued}
end
@ -709,6 +710,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
end
defp maybe_enqueue_terrain(terrain, contact) when not is_nil(terrain) do
_ =
if contact.terrain_status != :complete do
Radio.set_enrichment_status!([contact.id], :terrain_status, :complete)
end
@ -721,7 +723,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
contact.pos1 && contact.pos2 && contact.terrain_status != :queued ->
case Oban.insert(TerrainProfileWorker.new(%{"contact_id" => contact.id})) do
{:ok, %{conflict?: false}} ->
Radio.set_enrichment_status!([contact.id], :terrain_status, :queued)
_ = Radio.set_enrichment_status!([contact.id], :terrain_status, :queued)
%{contact | terrain_status: :queued}
_ ->
@ -737,6 +739,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
end
defp maybe_enqueue_hrrr(hrrr, contact) when not is_nil(hrrr) do
_ =
if contact.hrrr_status != :complete do
Radio.set_enrichment_status!([contact.id], :hrrr_status, :complete)
end
@ -765,6 +768,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
# Rust hrrr-point-worker picks them up alongside backfill work.
{:ok, _} = HrrrPointEnqueuer.enqueue(%{valid_time => [{rlat, rlon}]})
_ =
if contact.hrrr_status != :queued do
Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued)
end
@ -785,6 +789,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
lon = contact.pos1 && contact.pos1["lon"]
valid_time = NarrClient.snap_to_analysis_hour(contact.qso_timestamp)
_ =
if lat && lon && NarrClient.in_coverage?(valid_time) do
%{
"lat" => lat,

View file

@ -23,6 +23,7 @@ defmodule MicrowavepropWeb.ImportLive do
|> push_navigate(to: ~p"/submit")}
%ImportRun{} = run ->
_ =
if connected?(socket) do
Phoenix.PubSub.subscribe(@pubsub, "csv_import:#{run.id}")
end

View file

@ -35,11 +35,12 @@ defmodule MicrowavepropWeb.MapLive do
@impl true
def mount(params, session, socket) do
_ =
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
Process.send_after(self(), :refresh_pipeline_status, @pipeline_status_refresh_ms)
Process.send_after(self(), :advance_now_cursor, @advance_now_cursor_ms)
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
_ = Process.send_after(self(), :refresh_pipeline_status, @pipeline_status_refresh_ms)
_ = Process.send_after(self(), :advance_now_cursor, @advance_now_cursor_ms)
end
socket =

View file

@ -33,6 +33,7 @@ defmodule MicrowavepropWeb.PathLive do
@impl true
def mount(_params, _session, socket) do
_ =
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
end

View file

@ -22,6 +22,7 @@ defmodule MicrowavepropWeb.RoverLive do
@impl true
def mount(_params, _session, socket) do
_ =
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
end

View file

@ -11,9 +11,10 @@ defmodule MicrowavepropWeb.StatusLive do
@impl true
def mount(_params, _session, socket) do
_ =
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
# Rust prop-grid-rs emits NOTIFY propagation_ready on completion —
# PropagationNotifyListener fans it out as `propagation:updated` so
# the grid_tasks panel transitions from "running" → "done" without

View file

@ -138,8 +138,9 @@ defmodule MicrowavepropWeb.WeatherMapLive do
@impl true
def mount(_params, _session, socket) do
_ =
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "weather:updated")
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "weather:updated")
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
end

View file

@ -91,6 +91,7 @@ defmodule MicrowavepropWeb.UserAuth do
user_token = get_session(conn, :user_token)
user_token && Accounts.delete_user_session_token(user_token)
_ =
if live_socket_id = get_session(conn, :live_socket_id) do
MicrowavepropWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
end

View file

@ -34,7 +34,7 @@ defmodule Mix.Tasks.Backtest do
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} =
OptionParser.parse(argv,

View file

@ -28,7 +28,7 @@ defmodule Mix.Tasks.HrrrBackfill do
@impl Mix.Task
def run(args) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} = OptionParser.parse(args, switches: [all: :boolean, limit: :integer])
refetch_all? = Keyword.get(opts, :all, false)

View file

@ -17,7 +17,7 @@ defmodule Mix.Tasks.HrrrClimatology do
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} = OptionParser.parse(argv, switches: [min_samples: :integer])
min_samples = Keyword.get(opts, :min_samples, 3)

View file

@ -26,7 +26,7 @@ defmodule Mix.Tasks.HrrrNativeBackfill do
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
limit = Keyword.get(opts, :limit, 50)

View file

@ -23,7 +23,7 @@ defmodule Mix.Tasks.HrrrNativeDeriveFields do
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
limit = Keyword.get(opts, :limit, 10_000)

View file

@ -21,7 +21,7 @@ defmodule Mix.Tasks.ImportContestLogs do
end
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
# Concatenate all CSVs into one string (skip headers after the first file)
combined =

View file

@ -24,7 +24,7 @@ defmodule Mix.Tasks.NexradBackfill do
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
limit = Keyword.get(opts, :limit, 200)

View file

@ -23,7 +23,7 @@ defmodule Mix.Tasks.PropagationGrid do
IO.puts("Starting propagation grid computation...")
PropagationGridWorker.perform(%Oban.Job{args: %{}})
_ = PropagationGridWorker.perform(%Oban.Job{args: %{}})
IO.puts("Done!")
end
end

View file

@ -57,8 +57,8 @@ defmodule Mix.Tasks.RadarBackfill do
|> Enum.with_index(1)
|> Enum.each(fn {batch, i} ->
jobs = Enum.map(batch, &CommonVolumeRadarWorker.new(%{"contact_id" => &1}))
Oban.insert_all(jobs)
Radio.set_enrichment_status!(batch, :radar_status, :queued)
_ = Oban.insert_all(jobs)
_ = Radio.set_enrichment_status!(batch, :radar_status, :queued)
Mix.shell().info(" batch #{i}: #{length(batch)} enqueued")
end)

View file

@ -25,7 +25,7 @@ defmodule Mix.Tasks.RecalibrateScorer do
@impl Mix.Task
def run(argv) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{opts, _, _} =
OptionParser.parse(argv,

View file

@ -21,7 +21,7 @@ defmodule Mix.Tasks.ResetEnrichment do
@impl Mix.Task
def run(_args) do
Mix.Task.run("app.start")
Oban.pause_all_queues(Oban)
_ = Oban.pause_all_queues(Oban)
{count, _} =
Contact

View file

@ -239,6 +239,7 @@ defmodule Mix.Tasks.PropagationAnalyze do
end)
|> Enum.sort_by(fn {_, rho, _} -> -abs(rho || 0) end)
_ =
for {label, rho, n} <- correlations do
rho_str = if rho, do: format_num(rho, 4), else: "N/A"
@ -353,11 +354,12 @@ defmodule Mix.Tasks.PropagationAnalyze do
IO.puts("\n#{format_band(band)} (n=#{length(band_data)}):")
_ =
for {key, label, bins} <- factor_configs do
print_binned_factor(band_data, key, label, bins)
_ = print_binned_factor(band_data, key, label, bins)
end
print_categorical_factor(band_data, terrain_config)
_ = print_categorical_factor(band_data, terrain_config)
end
IO.puts("")

View file

@ -102,7 +102,7 @@ defmodule Mix.Tasks.PropagationTrain do
Model.save(trained_state)
IO.puts("Done.")
check_monthly_bias(trained_state)
_ = check_monthly_bias(trained_state)
"-" |> String.duplicate(70) |> IO.puts()
IO.puts("Finished: #{DateTime.to_string(DateTime.utc_now())}")

View file

@ -21,7 +21,8 @@ defmodule Microwaveprop.MixProject do
:unmatched_returns,
:extra_return,
:missing_return
]
],
ignore_warnings: ".dialyzer_ignore.exs"
]
]
end