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:
parent
733a7f5bf1
commit
d61fbd346e
61 changed files with 354 additions and 309 deletions
10
.dialyzer_ignore.exs
Normal file
10
.dialyzer_ignore.exs
Normal 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}
|
||||||
|
]
|
||||||
|
|
@ -18,6 +18,8 @@ defmodule Microwaveprop.Accounts.Scope do
|
||||||
|
|
||||||
alias Microwaveprop.Accounts.User
|
alias Microwaveprop.Accounts.User
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{user: User.t() | nil}
|
||||||
|
|
||||||
defstruct user: nil
|
defstruct user: nil
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ defmodule Microwaveprop.Application do
|
||||||
result = Supervisor.start_link(children, opts)
|
result = Supervisor.start_link(children, opts)
|
||||||
|
|
||||||
# Run migrations after Repo is started
|
# Run migrations after Repo is started
|
||||||
Microwaveprop.Release.migrate()
|
_ = Microwaveprop.Release.migrate()
|
||||||
|
|
||||||
# Warm GridCache from the latest persisted ProfilesFile so /weather
|
# Warm GridCache from the latest persisted ProfilesFile so /weather
|
||||||
# has data immediately after a pod restart. Runs after the GridCache
|
# 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
|
# 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
|
# heap and never GC (it's idle after boot), pinning ~800 MiB for the
|
||||||
# lifetime of the pod.
|
# 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)
|
# Load ML model in dev/test only (Nx/Axon not compiled for prod)
|
||||||
if Application.get_env(:microwaveprop, :load_ml_model, false) do
|
if Application.get_env(:microwaveprop, :load_ml_model, false) do
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,7 @@ defmodule Microwaveprop.Backtest.Features do
|
||||||
it documents the intended API; the backtest will only work once
|
it documents the intended API; the backtest will only work once
|
||||||
the pipeline is live.
|
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
|
def distance_to_front(_lat, _lon, _valid_time), do: nil
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -278,7 +278,7 @@ defmodule Microwaveprop.Backtest.Features do
|
||||||
bearing, so this is only usable in QSO-level scoring, not grid
|
bearing, so this is only usable in QSO-level scoring, not grid
|
||||||
scoring. Placeholder until Phase 5.4.
|
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
|
def parallel_to_front(_lat, _lon, _valid_time), do: nil
|
||||||
|
|
||||||
@doc "Duct usable for 10 GHz."
|
@doc "Duct usable for 10 GHz."
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ defmodule Microwaveprop.Beacons do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Gets a single beacon. Raises if not found."
|
@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)
|
def get_beacon!(id), do: Beacon |> Repo.get!(id) |> Repo.preload(:user)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -119,7 +119,7 @@ defmodule Microwaveprop.Beacons do
|
||||||
def delete_beacon(%Beacon{} = beacon) do
|
def delete_beacon(%Beacon{} = beacon) do
|
||||||
case Repo.delete(beacon) do
|
case Repo.delete(beacon) do
|
||||||
{:ok, beacon} ->
|
{:ok, beacon} ->
|
||||||
broadcast({:deleted, beacon})
|
_ = broadcast({:deleted, beacon})
|
||||||
{:ok, beacon}
|
{:ok, beacon}
|
||||||
|
|
||||||
other ->
|
other ->
|
||||||
|
|
@ -134,7 +134,7 @@ defmodule Microwaveprop.Beacons do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp broadcast_if_ok({:ok, beacon} = result, type) do
|
defp broadcast_if_ok({:ok, beacon} = result, type) do
|
||||||
broadcast({type, beacon})
|
_ = broadcast({type, beacon})
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,13 +58,14 @@ defmodule Microwaveprop.Cache do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_opts) do
|
def init(_opts) do
|
||||||
:ets.new(@table, [
|
_ =
|
||||||
:set,
|
:ets.new(@table, [
|
||||||
:named_table,
|
:set,
|
||||||
:public,
|
:named_table,
|
||||||
read_concurrency: true,
|
:public,
|
||||||
write_concurrency: true
|
read_concurrency: true,
|
||||||
])
|
write_concurrency: true
|
||||||
|
])
|
||||||
|
|
||||||
{:ok, %{}}
|
{:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,6 @@ defmodule Microwaveprop.Ionosphere.GiroClient do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_int(nil), do: nil
|
|
||||||
|
|
||||||
defp parse_int(s) do
|
defp parse_int(s) do
|
||||||
case Integer.parse(s) do
|
case Integer.parse(s) do
|
||||||
{n, ""} -> n
|
{n, ""} -> n
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ defmodule Microwaveprop.Propagation.FreshnessMonitor do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info(:check, state) do
|
def handle_info(:check, state) do
|
||||||
check_freshness()
|
_ = check_freshness()
|
||||||
Process.send_after(self(), :check, @check_interval)
|
Process.send_after(self(), :check, @check_interval)
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ defmodule Microwaveprop.Propagation.NotifyListener do
|
||||||
# unbounded when Rust covers many forecast hours back-to-back.
|
# unbounded when Rust covers many forecast hours back-to-back.
|
||||||
ScoreCache.prune_older_than(DateTime.add(DateTime.utc_now(), -2, :hour))
|
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
|
if err > 0 do
|
||||||
Logger.warning("NotifyListener: warmed #{ok} bands, #{err} failed for valid_time=#{valid_time}")
|
Logger.warning("NotifyListener: warmed #{ok} bands, #{err} failed for valid_time=#{valid_time}")
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ defmodule Microwaveprop.Propagation.RunTiming do
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
@foreign_key_type :binary_id
|
@foreign_key_type :binary_id
|
||||||
schema "propagation_run_timings" do
|
schema "propagation_run_timings" do
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ defmodule Microwaveprop.Propagation.ScoreCache do
|
||||||
"""
|
"""
|
||||||
@spec broadcast_put(non_neg_integer(), DateTime.t(), [score()]) :: :ok
|
@spec broadcast_put(non_neg_integer(), DateTime.t(), [score()]) :: :ok
|
||||||
def broadcast_put(band_mhz, valid_time, scores) do
|
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
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -122,8 +122,8 @@ defmodule Microwaveprop.Propagation.ScoreCache do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_opts) do
|
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])
|
||||||
PubSub.subscribe(@pubsub, @topic)
|
:ok = PubSub.subscribe(@pubsub, @topic)
|
||||||
{:ok, %{}}
|
{:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,10 @@ defmodule Microwaveprop.Propagation.ScoreCacheReconciler do
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
interval = Keyword.get(opts, :interval_ms, @default_interval_ms)
|
interval = Keyword.get(opts, :interval_ms, @default_interval_ms)
|
||||||
|
|
||||||
if Keyword.get(opts, :run_on_start, true) do
|
_ =
|
||||||
Process.send_after(self(), :sweep, 500)
|
if Keyword.get(opts, :run_on_start, true) do
|
||||||
end
|
_ = Process.send_after(self(), :sweep, 500)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, %{interval_ms: interval}}
|
{:ok, %{interval_ms: interval}}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -980,7 +980,7 @@ defmodule Microwaveprop.Radio do
|
||||||
|
|
||||||
# Apply changes to contact
|
# Apply changes to contact
|
||||||
contact = Repo.get!(Contact, edit.contact_id)
|
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])
|
Repo.preload(approved, [:user, :contact, :reviewed_by])
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ defmodule Microwaveprop.Release do
|
||||||
@app :microwaveprop
|
@app :microwaveprop
|
||||||
|
|
||||||
def migrate do
|
def migrate do
|
||||||
load_app()
|
_ = load_app()
|
||||||
|
|
||||||
for repo <- repos() do
|
for repo <- repos() do
|
||||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
|
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
|
||||||
|
|
@ -29,30 +29,30 @@ defmodule Microwaveprop.Release do
|
||||||
end
|
end
|
||||||
|
|
||||||
def rollback(repo, version) do
|
def rollback(repo, version) do
|
||||||
load_app()
|
_ = load_app()
|
||||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
||||||
end
|
end
|
||||||
|
|
||||||
def backtest(feature_name) when is_binary(feature_name) do
|
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}))
|
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "backtest", feature: feature_name}))
|
||||||
IO.puts("Enqueued backtest for #{feature_name} (job #{job.id})")
|
IO.puts("Enqueued backtest for #{feature_name} (job #{job.id})")
|
||||||
end
|
end
|
||||||
|
|
||||||
def backtest_all do
|
def backtest_all do
|
||||||
start_app()
|
_ = start_app()
|
||||||
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "backtest_all"}))
|
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "backtest_all"}))
|
||||||
IO.puts("Enqueued consolidated backtest (job #{job.id})")
|
IO.puts("Enqueued consolidated backtest (job #{job.id})")
|
||||||
end
|
end
|
||||||
|
|
||||||
def climatology(min_samples \\ 3) do
|
def climatology(min_samples \\ 3) do
|
||||||
start_app()
|
_ = start_app()
|
||||||
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "climatology", min_samples: min_samples}))
|
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "climatology", min_samples: min_samples}))
|
||||||
IO.puts("Enqueued climatology build (job #{job.id})")
|
IO.puts("Enqueued climatology build (job #{job.id})")
|
||||||
end
|
end
|
||||||
|
|
||||||
def native_backfill(limit \\ 500) do
|
def native_backfill(limit \\ 500) do
|
||||||
start_app()
|
_ = start_app()
|
||||||
|
|
||||||
%{rows: rows} =
|
%{rows: rows} =
|
||||||
Microwaveprop.Repo.query!(
|
Microwaveprop.Repo.query!(
|
||||||
|
|
@ -88,7 +88,7 @@ defmodule Microwaveprop.Release do
|
||||||
end
|
end
|
||||||
|
|
||||||
def recalibrate do
|
def recalibrate do
|
||||||
start_app()
|
_ = start_app()
|
||||||
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "recalibrate"}))
|
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "recalibrate"}))
|
||||||
IO.puts("Enqueued recalibration (job #{job.id})")
|
IO.puts("Enqueued recalibration (job #{job.id})")
|
||||||
end
|
end
|
||||||
|
|
@ -98,12 +98,12 @@ defmodule Microwaveprop.Release do
|
||||||
# files on disk. Left as a stub that tells the operator what
|
# files on disk. Left as a stub that tells the operator what
|
||||||
# happened if they shell into a running release from muscle memory.
|
# happened if they shell into a running release from muscle memory.
|
||||||
def scorer_diff(_new_weights_json) do
|
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.")
|
IO.puts("scorer_diff is disabled: propagation_scores table + factors storage have been dropped.")
|
||||||
end
|
end
|
||||||
|
|
||||||
def native_derive(limit \\ 10_000) do
|
def native_derive(limit \\ 10_000) do
|
||||||
start_app()
|
_ = start_app()
|
||||||
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "native_derive", limit: limit}))
|
{:ok, job} = Oban.insert(AdminTaskWorker.new(%{task: "native_derive", limit: limit}))
|
||||||
IO.puts("Enqueued native derive (job #{job.id})")
|
IO.puts("Enqueued native derive (job #{job.id})")
|
||||||
end
|
end
|
||||||
|
|
@ -113,7 +113,7 @@ defmodule Microwaveprop.Release do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load_app do
|
defp load_app do
|
||||||
Application.ensure_all_started(:ssl)
|
{:ok, _} = Application.ensure_all_started(:ssl)
|
||||||
Application.ensure_loaded(@app)
|
Application.ensure_loaded(@app)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,33 +38,35 @@ defmodule Microwaveprop.RepoListener do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:notification, _pid, _ref, "contact_status_changed", payload}, state) do
|
def handle_info({:notification, _pid, _ref, "contact_status_changed", payload}, state) do
|
||||||
case Jason.decode(payload) do
|
_ =
|
||||||
{:ok, data} ->
|
case Jason.decode(payload) do
|
||||||
Phoenix.PubSub.broadcast(
|
{:ok, data} ->
|
||||||
Microwaveprop.PubSub,
|
Phoenix.PubSub.broadcast(
|
||||||
"db:contact_status",
|
Microwaveprop.PubSub,
|
||||||
{:contact_status_changed, data}
|
"db:contact_status",
|
||||||
)
|
{:contact_status_changed, data}
|
||||||
|
)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:notification, _pid, _ref, "oban_job_changed", payload}, state) do
|
def handle_info({:notification, _pid, _ref, "oban_job_changed", payload}, state) do
|
||||||
case Jason.decode(payload) do
|
_ =
|
||||||
{:ok, data} ->
|
case Jason.decode(payload) do
|
||||||
Phoenix.PubSub.broadcast(
|
{:ok, data} ->
|
||||||
Microwaveprop.PubSub,
|
Phoenix.PubSub.broadcast(
|
||||||
"db:oban_jobs",
|
Microwaveprop.PubSub,
|
||||||
{:oban_job_changed, data}
|
"db:oban_jobs",
|
||||||
)
|
{:oban_job_changed, data}
|
||||||
|
)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ defmodule Microwaveprop.Terrain.Srtm do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec fetch_elevation_profile(float(), float(), float(), float(), String.t(), pos_integer(), keyword()) ::
|
@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
|
def fetch_elevation_profile(lat1, lon1, lat2, lon2, tiles_dir, n \\ 64, opts \\ []) do
|
||||||
pts = sample_path(lat1, lon1, lat2, lon2, n)
|
pts = sample_path(lat1, lon1, lat2, lon2, n)
|
||||||
dist_km = haversine_km(lat1, lon1, lat2, lon2)
|
dist_km = haversine_km(lat1, lon1, lat2, lon2)
|
||||||
|
|
@ -130,7 +130,7 @@ defmodule Microwaveprop.Terrain.Srtm do
|
||||||
{:error, :void}
|
{:error, :void}
|
||||||
end
|
end
|
||||||
|
|
||||||
:file.close(fd)
|
_ = :file.close(fd)
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -527,27 +527,30 @@ defmodule Microwaveprop.Weather do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp kickoff_async_grid_fill(valid_time) do
|
defp kickoff_async_grid_fill(valid_time) do
|
||||||
if GridCache.claim_fill(valid_time) do
|
_ =
|
||||||
Task.start(fn ->
|
if GridCache.claim_fill(valid_time) do
|
||||||
try do
|
{:ok, _pid} =
|
||||||
Logger.info("Weather.grid_cache async fill starting for #{valid_time}")
|
Task.start(fn ->
|
||||||
warm_grid_cache_and_broadcast(valid_time)
|
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,
|
Phoenix.PubSub.broadcast(
|
||||||
"weather:updated",
|
Microwaveprop.PubSub,
|
||||||
{:weather_updated, valid_time}
|
"weather:updated",
|
||||||
)
|
{:weather_updated, valid_time}
|
||||||
|
)
|
||||||
|
|
||||||
Logger.info("Weather.grid_cache async fill complete for #{valid_time}")
|
Logger.info("Weather.grid_cache async fill complete for #{valid_time}")
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
Logger.error("Weather.grid_cache async fill failed: #{inspect(e)}")
|
Logger.error("Weather.grid_cache async fill failed: #{inspect(e)}")
|
||||||
after
|
after
|
||||||
GridCache.release_fill(valid_time)
|
GridCache.release_fill(valid_time)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ defmodule Microwaveprop.Weather.FrontalAnalysis do
|
||||||
@doc """
|
@doc """
|
||||||
Angle between a QSO path and the nearest front (0° = parallel, 90° = crosses).
|
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
|
def path_front_angle(path_bearing_deg, front_bearing_deg) do
|
||||||
diff = abs(path_bearing_deg - front_bearing_deg)
|
diff = abs(path_bearing_deg - front_bearing_deg)
|
||||||
diff = if diff > 180, do: 360 - diff, else: diff
|
diff = if diff > 180, do: 360 - diff, else: diff
|
||||||
|
|
|
||||||
|
|
@ -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
|
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()) ::
|
@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
|
def linear_index({i, j}, nx, _scan_mode) do
|
||||||
j * nx + i
|
j * nx + i
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do
|
||||||
{:error, "wgrib2 failed (exit #{exit_code}): #{String.slice(output, 0, 200)}"}
|
{:error, "wgrib2 failed (exit #{exit_code}): #{String.slice(output, 0, 200)}"}
|
||||||
end
|
end
|
||||||
after
|
after
|
||||||
File.rm(tmp_grib)
|
_ = File.rm(tmp_grib)
|
||||||
File.rm(tmp_bin)
|
File.rm(tmp_bin)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ defmodule Microwaveprop.Weather.GridCache do
|
||||||
@doc "Insert locally AND broadcast to peer nodes via PubSub."
|
@doc "Insert locally AND broadcast to peer nodes via PubSub."
|
||||||
@spec broadcast_put(DateTime.t(), [row()]) :: :ok
|
@spec broadcast_put(DateTime.t(), [row()]) :: :ok
|
||||||
def broadcast_put(valid_time, rows) do
|
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
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -122,9 +122,9 @@ defmodule Microwaveprop.Weather.GridCache do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_opts) do
|
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])
|
||||||
:ets.new(@lock_table, [:set, :named_table, :protected])
|
_ = :ets.new(@lock_table, [:set, :named_table, :protected])
|
||||||
PubSub.subscribe(@pubsub, @topic)
|
:ok = PubSub.subscribe(@pubsub, @topic)
|
||||||
{:ok, %{monitors: %{}}}
|
{:ok, %{monitors: %{}}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,46 +50,47 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do
|
||||||
# || union on round-trip.
|
# || union on round-trip.
|
||||||
id = Ecto.UUID.bingenerate()
|
id = Ecto.UUID.bingenerate()
|
||||||
|
|
||||||
Repo.insert_all(
|
_ =
|
||||||
"hrrr_fetch_tasks",
|
Repo.insert_all(
|
||||||
[
|
"hrrr_fetch_tasks",
|
||||||
%{
|
[
|
||||||
id: id,
|
%{
|
||||||
valid_time: valid_time,
|
id: id,
|
||||||
points: json_points,
|
valid_time: valid_time,
|
||||||
status: "queued",
|
points: json_points,
|
||||||
attempt: 0,
|
status: "queued",
|
||||||
inserted_at: now,
|
attempt: 0,
|
||||||
updated_at: now
|
inserted_at: now,
|
||||||
}
|
updated_at: now
|
||||||
],
|
}
|
||||||
on_conflict:
|
],
|
||||||
from(t in "hrrr_fetch_tasks",
|
on_conflict:
|
||||||
update: [
|
from(t in "hrrr_fetch_tasks",
|
||||||
set: [
|
update: [
|
||||||
points:
|
set: [
|
||||||
fragment(
|
points:
|
||||||
"(SELECT COALESCE(jsonb_agg(DISTINCT p), '[]'::jsonb) FROM jsonb_array_elements(? || EXCLUDED.points) AS p)",
|
fragment(
|
||||||
t.points
|
"(SELECT COALESCE(jsonb_agg(DISTINCT p), '[]'::jsonb) FROM jsonb_array_elements(? || EXCLUDED.points) AS p)",
|
||||||
),
|
t.points
|
||||||
status:
|
),
|
||||||
fragment(
|
status:
|
||||||
"CASE WHEN ? IN ('done', 'failed') THEN 'queued' ELSE ? END",
|
fragment(
|
||||||
t.status,
|
"CASE WHEN ? IN ('done', 'failed') THEN 'queued' ELSE ? END",
|
||||||
t.status
|
t.status,
|
||||||
),
|
t.status
|
||||||
attempt:
|
),
|
||||||
fragment(
|
attempt:
|
||||||
"CASE WHEN ? IN ('done', 'failed') THEN 0 ELSE ? END",
|
fragment(
|
||||||
t.status,
|
"CASE WHEN ? IN ('done', 'failed') THEN 0 ELSE ? END",
|
||||||
t.attempt
|
t.status,
|
||||||
),
|
t.attempt
|
||||||
updated_at: ^now
|
),
|
||||||
|
updated_at: ^now
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
),
|
||||||
),
|
conflict_target: [:valid_time]
|
||||||
conflict_target: [:valid_time]
|
)
|
||||||
)
|
|
||||||
|
|
||||||
acc + 1
|
acc + 1
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ defmodule Microwaveprop.Weather.NexradCache do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_opts) do
|
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, %{}}
|
{:ok, %{}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -72,11 +72,12 @@ defmodule Microwaveprop.Workers.BackfillEnqueueWorker do
|
||||||
|
|
||||||
Enum.each(contacts, &ContactWeatherEnqueueWorker.enqueue_for_contact(&1, types))
|
Enum.each(contacts, &ContactWeatherEnqueueWorker.enqueue_for_contact(&1, types))
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast(
|
_ =
|
||||||
Microwaveprop.PubSub,
|
Phoenix.PubSub.broadcast(
|
||||||
"backfill:enqueue_complete",
|
Microwaveprop.PubSub,
|
||||||
{:enqueue_complete, count}
|
"backfill:enqueue_complete",
|
||||||
)
|
{:enqueue_complete, count}
|
||||||
|
)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,10 @@ defmodule Microwaveprop.Workers.CanadianSoundingFetchWorker do
|
||||||
def most_recent_sounding_time(%DateTime{} = now) do
|
def most_recent_sounding_time(%DateTime{} = now) do
|
||||||
cutoff = DateTime.add(now, -@publish_delay_seconds, :second)
|
cutoff = DateTime.add(now, -@publish_delay_seconds, :second)
|
||||||
|
|
||||||
cond do
|
if cutoff.hour >= 12 do
|
||||||
cutoff.hour >= 12 ->
|
%{cutoff | hour: 12, minute: 0, second: 0, microsecond: {0, 0}}
|
||||||
%{cutoff | hour: 12, minute: 0, second: 0, microsecond: {0, 0}}
|
else
|
||||||
|
%{cutoff | hour: 0, 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 ->
|
|
||||||
%{cutoff | hour: 0, minute: 0, second: 0, microsecond: {0, 0}}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -91,7 +89,7 @@ defmodule Microwaveprop.Workers.CanadianSoundingFetchWorker do
|
||||||
case UwyoSoundingClient.fetch_sounding(uwyo_code, sounding_time) do
|
case UwyoSoundingClient.fetch_sounding(uwyo_code, sounding_time) do
|
||||||
{:ok, [%{profile: profile} | _]} when profile != [] ->
|
{:ok, [%{profile: profile} | _]} when profile != [] ->
|
||||||
attrs = build_attrs(sounding_time, 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")
|
Logger.info("CanadianSoundings: #{code} ingested sounding with #{length(profile)} levels")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ defmodule Microwaveprop.Workers.ContactImportWorker do
|
||||||
updates = build_counter_updates(commit_result, length(chunk))
|
updates = build_counter_updates(commit_result, length(chunk))
|
||||||
updated_run = apply_chunk_updates(run.id, updates)
|
updated_run = apply_chunk_updates(run.id, updates)
|
||||||
|
|
||||||
broadcast(run.id, updated_run)
|
_ = broadcast(run.id, updated_run)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,16 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
# and UPSERT-union means a backfill tick that adds a new contact in
|
# and UPSERT-union means a backfill tick that adds a new contact in
|
||||||
# an already-scheduled hour collapses into the existing row instead
|
# an already-scheduled hour collapses into the existing row instead
|
||||||
# of creating a duplicate fetch.
|
# of creating a duplicate fetch.
|
||||||
if :hrrr in types do
|
_ =
|
||||||
{:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])
|
if :hrrr in types do
|
||||||
end
|
{:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts([contact])
|
||||||
|
end
|
||||||
|
|
||||||
# NARR jobs must go through Oban.insert/1 so the worker's unique
|
# NARR jobs must go through Oban.insert/1 so the worker's unique
|
||||||
# constraint is honored; Oban OSS insert_all does not check uniqueness.
|
# constraint is honored; Oban OSS insert_all does not check uniqueness.
|
||||||
insert_unique(jobs_by_type[:narr])
|
insert_unique(jobs_by_type[:narr])
|
||||||
|
|
||||||
mark_enrichment_statuses(contact, types, jobs_by_type)
|
_ = mark_enrichment_statuses(contact, types, jobs_by_type)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
@ -97,9 +98,10 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
defp mark_enrichment_statuses(contact, types, jobs_by_type) do
|
defp mark_enrichment_statuses(contact, types, jobs_by_type) do
|
||||||
ids = [contact.id]
|
ids = [contact.id]
|
||||||
|
|
||||||
if contact.pos1 do
|
_ =
|
||||||
mark_pos1_statuses(contact, ids, types, jobs_by_type)
|
if contact.pos1 do
|
||||||
end
|
mark_pos1_statuses(contact, ids, types, jobs_by_type)
|
||||||
|
end
|
||||||
|
|
||||||
if contact.pos1 && contact.pos2 do
|
if contact.pos1 && contact.pos2 do
|
||||||
mark_path_statuses(contact, ids, types, jobs_by_type)
|
mark_path_statuses(contact, ids, types, jobs_by_type)
|
||||||
|
|
@ -113,22 +115,22 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp mark_missing_path_statuses(ids, types) do
|
defp mark_missing_path_statuses(ids, types) do
|
||||||
if :terrain in types, do: Radio.set_enrichment_status!(ids, :terrain_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 :radar in types, do: Radio.set_enrichment_status!(ids, :radar_status, :unavailable)
|
||||||
|
|
||||||
if :mechanism in types,
|
if :mechanism in types,
|
||||||
do: Radio.set_enrichment_status!(ids, :mechanism_status, :unavailable)
|
do: Radio.set_enrichment_status!(ids, :mechanism_status, :unavailable)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp mark_pos1_statuses(contact, ids, types, jobs_by_type) do
|
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 :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 :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])
|
if :iemre in types, do: mark_status!(ids, :iemre_status, jobs_by_type[:iemre])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp mark_path_statuses(contact, ids, types, jobs_by_type) do
|
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 :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 :radar in types, do: mark_radar_status!(contact, ids, jobs_by_type[:radar])
|
||||||
|
|
||||||
if :mechanism in types,
|
if :mechanism in types,
|
||||||
do: mark_status!(ids, :mechanism_status, jobs_by_type[:mechanism])
|
do: mark_status!(ids, :mechanism_status, jobs_by_type[:mechanism])
|
||||||
|
|
@ -178,7 +180,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
:ok
|
:ok
|
||||||
|
|
||||||
contacts ->
|
contacts ->
|
||||||
Radio.backfill_distances(contacts)
|
_ = Radio.backfill_distances(contacts)
|
||||||
|
|
||||||
jobs = build_weather_jobs(contacts)
|
jobs = build_weather_jobs(contacts)
|
||||||
|
|
||||||
|
|
@ -187,7 +189,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
end
|
end
|
||||||
|
|
||||||
ids = Enum.map(contacts, & &1.id)
|
ids = Enum.map(contacts, & &1.id)
|
||||||
Radio.mark_weather_queued!(ids)
|
_ = Radio.mark_weather_queued!(ids)
|
||||||
|
|
||||||
enqueue_weather_jobs()
|
enqueue_weather_jobs()
|
||||||
end
|
end
|
||||||
|
|
@ -206,7 +208,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
{:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts(contacts)
|
{:ok, _} = HrrrPointEnqueuer.enqueue_for_contacts(contacts)
|
||||||
|
|
||||||
ids = Enum.map(contacts, & &1.id)
|
ids = Enum.map(contacts, & &1.id)
|
||||||
Radio.mark_hrrr_queued!(ids)
|
_ = Radio.mark_hrrr_queued!(ids)
|
||||||
|
|
||||||
enqueue_hrrr_jobs()
|
enqueue_hrrr_jobs()
|
||||||
end
|
end
|
||||||
|
|
@ -225,7 +227,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
end
|
end
|
||||||
|
|
||||||
ids = Enum.map(contacts, & &1.id)
|
ids = Enum.map(contacts, & &1.id)
|
||||||
Radio.mark_terrain_queued!(ids)
|
_ = Radio.mark_terrain_queued!(ids)
|
||||||
|
|
||||||
enqueue_terrain_jobs()
|
enqueue_terrain_jobs()
|
||||||
end
|
end
|
||||||
|
|
@ -350,7 +352,7 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
end
|
end
|
||||||
|
|
||||||
ids = Enum.map(contacts, & &1.id)
|
ids = Enum.map(contacts, & &1.id)
|
||||||
Radio.mark_iemre_queued!(ids)
|
_ = Radio.mark_iemre_queued!(ids)
|
||||||
|
|
||||||
enqueue_iemre_jobs()
|
enqueue_iemre_jobs()
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -175,14 +175,15 @@ defmodule Microwaveprop.Workers.GefsFetchWorker do
|
||||||
end)
|
end)
|
||||||
|> Enum.to_list()
|
|> Enum.to_list()
|
||||||
|
|
||||||
case Propagation.replace_scores(scores, valid_time) do
|
_ =
|
||||||
{:ok, count} ->
|
case Propagation.replace_scores(scores, valid_time) do
|
||||||
Logger.info("GefsFetch: wrote #{count} scores for fh=#{fh} @ #{valid_time}")
|
{:ok, count} ->
|
||||||
broadcast_updated(valid_time)
|
Logger.info("GefsFetch: wrote #{count} scores for fh=#{fh} @ #{valid_time}")
|
||||||
|
broadcast_updated(valid_time)
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
Logger.error("GefsFetch: replace_scores failed fh=#{fh}: #{inspect(error)}")
|
Logger.error("GefsFetch: replace_scores failed fh=#{fh}: #{inspect(error)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
attrs = Enum.map(profiles, &build_profile_attrs(run_time, fh, &1))
|
attrs = Enum.map(profiles, &build_profile_attrs(run_time, fh, &1))
|
||||||
{count, _} = Weather.upsert_gefs_profiles_batch(attrs)
|
{count, _} = Weather.upsert_gefs_profiles_batch(attrs)
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,18 @@ defmodule Microwaveprop.Workers.IemreFetchWorker do
|
||||||
case IemClient.fetch_iemre(lat, lon, date) do
|
case IemClient.fetch_iemre(lat, lon, date) do
|
||||||
{:ok, []} ->
|
{:ok, []} ->
|
||||||
# Store stub so this lat/lon/date isn't retried on future backfills
|
# 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")
|
Logger.info("IEMRE: no data available for #{lat},#{lon} @ #{date_str}, stored stub")
|
||||||
:ok
|
:ok
|
||||||
|
|
||||||
{:ok, data} ->
|
{:ok, data} ->
|
||||||
Weather.upsert_iemre_observation(%{
|
_ =
|
||||||
lat: lat,
|
Weather.upsert_iemre_observation(%{
|
||||||
lon: lon,
|
lat: lat,
|
||||||
date: date,
|
lon: lon,
|
||||||
hourly: data
|
date: date,
|
||||||
})
|
hourly: data
|
||||||
|
})
|
||||||
|
|
||||||
Logger.info("IEMRE observation saved for #{lat},#{lon} @ #{date_str} (#{length(data)} hours)")
|
Logger.info("IEMRE observation saved for #{lat},#{lon} @ #{date_str} (#{length(data)} hours)")
|
||||||
:ok
|
:ok
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ defmodule Microwaveprop.Workers.NarrFetchWorker do
|
||||||
defp fetch_and_insert(rlat, rlon, valid_time) do
|
defp fetch_and_insert(rlat, rlon, valid_time) do
|
||||||
case NarrClient.fetch_profile_at(valid_time, {rlat, rlon}) do
|
case NarrClient.fetch_profile_at(valid_time, {rlat, rlon}) do
|
||||||
{:ok, attrs} ->
|
{:ok, attrs} ->
|
||||||
insert_profile(attrs, rlat, rlon, valid_time)
|
_ = insert_profile(attrs, rlat, rlon, valid_time)
|
||||||
:ok
|
:ok
|
||||||
|
|
||||||
{:error, reason} = error ->
|
{:error, reason} = error ->
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@ defmodule Microwaveprop.Workers.SolarIndexWorker do
|
||||||
matched = Enum.filter(all_records, fn r -> r.date == target_date end)
|
matched = Enum.filter(all_records, fn r -> r.date == target_date end)
|
||||||
Weather.upsert_solar_indices_batch(matched)
|
Weather.upsert_solar_indices_batch(matched)
|
||||||
|
|
||||||
if matched != [] do
|
_ =
|
||||||
Phoenix.PubSub.broadcast(
|
if matched != [] do
|
||||||
Microwaveprop.PubSub,
|
Phoenix.PubSub.broadcast(
|
||||||
"contact_enrichment:solar",
|
Microwaveprop.PubSub,
|
||||||
{:solar_ready, date_str}
|
"contact_enrichment:solar",
|
||||||
)
|
{:solar_ready, date_str}
|
||||||
end
|
)
|
||||||
|
end
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
|
||||||
def perform(%Oban.Job{args: %{"contact_id" => contact_id}}) do
|
def perform(%Oban.Job{args: %{"contact_id" => contact_id}}) do
|
||||||
Microwaveprop.Instrument.span([:worker, :terrain_profile], %{contact_id: contact_id}, fn ->
|
Microwaveprop.Instrument.span([:worker, :terrain_profile], %{contact_id: contact_id}, fn ->
|
||||||
if Terrain.has_terrain_profile?(contact_id) do
|
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
|
:ok
|
||||||
else
|
else
|
||||||
analyse_terrain(contact_id)
|
analyse_terrain(contact_id)
|
||||||
|
|
@ -38,7 +38,7 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
|
||||||
fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2)
|
fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2)
|
||||||
else
|
else
|
||||||
_ ->
|
_ ->
|
||||||
Radio.set_enrichment_status!([contact_id], :terrain_status, :unavailable)
|
_ = Radio.set_enrichment_status!([contact_id], :terrain_status, :unavailable)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -65,25 +65,27 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
|
||||||
%{"lat" => p.lat, "lon" => p.lon, "d" => p.d, "elev" => p.elev, "dist_km" => p.dist_km}
|
%{"lat" => p.lat, "lon" => p.lon, "d" => p.d, "elev" => p.elev, "dist_km" => p.dist_km}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Terrain.upsert_terrain_profile(%{
|
_ =
|
||||||
contact_id: contact_id,
|
Terrain.upsert_terrain_profile(%{
|
||||||
sample_count: length(profile),
|
contact_id: contact_id,
|
||||||
path_points: path_points,
|
sample_count: length(profile),
|
||||||
max_elevation_m: analysis.max_elevation_m,
|
path_points: path_points,
|
||||||
min_clearance_m: analysis.min_clearance_m,
|
max_elevation_m: analysis.max_elevation_m,
|
||||||
diffraction_db: analysis.diffraction_db,
|
min_clearance_m: analysis.min_clearance_m,
|
||||||
fresnel_hit_count: analysis.fresnel_hit_count,
|
diffraction_db: analysis.diffraction_db,
|
||||||
obstructed_count: analysis.obstructed_count,
|
fresnel_hit_count: analysis.fresnel_hit_count,
|
||||||
verdict: analysis.verdict
|
obstructed_count: analysis.obstructed_count,
|
||||||
})
|
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,
|
Phoenix.PubSub.broadcast(
|
||||||
"contact_enrichment:#{contact_id}",
|
Microwaveprop.PubSub,
|
||||||
{:terrain_ready, contact_id}
|
"contact_enrichment:#{contact_id}",
|
||||||
)
|
{:terrain_ready, contact_id}
|
||||||
|
)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -96,11 +96,12 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do
|
||||||
|
|
||||||
Logger.info("WeatherFetch ASOS: #{station_code} ingested #{count} observations")
|
Logger.info("WeatherFetch ASOS: #{station_code} ingested #{count} observations")
|
||||||
|
|
||||||
Phoenix.PubSub.broadcast(
|
_ =
|
||||||
Microwaveprop.PubSub,
|
Phoenix.PubSub.broadcast(
|
||||||
"contact_enrichment:weather",
|
Microwaveprop.PubSub,
|
||||||
{:weather_ready, %{station_id: station_id}}
|
"contact_enrichment:weather",
|
||||||
)
|
{:weather_ready, %{station_id: station_id}}
|
||||||
|
)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
@ -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
|
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
|
if Weather.has_sounding?(station.id, sounding_time) do
|
||||||
Logger.debug("WeatherFetch RAOB: #{station_code} already has sounding for #{sounding_time_str}")
|
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
|
:ok
|
||||||
else
|
else
|
||||||
Logger.info("WeatherFetch RAOB: fetching #{station_code} for #{sounding_time_str}")
|
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)
|
ingest_raob(station, station_id, station_code, parsed, contact_id)
|
||||||
|
|
||||||
{:ok, []} ->
|
{: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")
|
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
|
:ok
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
|
|
@ -145,20 +146,21 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do
|
||||||
|
|
||||||
defp ingest_raob(station, station_id, station_code, parsed, contact_id) do
|
defp ingest_raob(station, station_id, station_code, parsed, contact_id) do
|
||||||
sounding_attrs = build_sounding_attrs(parsed)
|
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")
|
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
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp broadcast_sounding_fetched(station_id, contact_id) do
|
defp broadcast_sounding_fetched(station_id, contact_id) do
|
||||||
Phoenix.PubSub.broadcast(
|
_ =
|
||||||
Microwaveprop.PubSub,
|
Phoenix.PubSub.broadcast(
|
||||||
"contact_enrichment:weather",
|
Microwaveprop.PubSub,
|
||||||
{:weather_ready, %{station_id: station_id}}
|
"contact_enrichment:weather",
|
||||||
)
|
{:weather_ready, %{station_id: station_id}}
|
||||||
|
)
|
||||||
|
|
||||||
if contact_id do
|
if contact_id do
|
||||||
Phoenix.PubSub.broadcast(
|
Phoenix.PubSub.broadcast(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ defmodule MicrowavepropWeb.HealthController do
|
||||||
|
|
||||||
def check(conn, _params) do
|
def check(conn, _params) do
|
||||||
# Verify DB is reachable
|
# Verify DB is reachable
|
||||||
SQL.query!(Microwaveprop.Repo, "SELECT 1")
|
_ = SQL.query!(Microwaveprop.Repo, "SELECT 1")
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/plain")
|
|> put_resp_content_type("text/plain")
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,14 @@ defmodule MicrowavepropWeb.UserResetPasswordController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(conn, %{"user" => %{"email" => email}}) do
|
def create(conn, %{"user" => %{"email" => email}}) do
|
||||||
if user = Accounts.get_user_by_email(email) do
|
_ =
|
||||||
{:ok, _email} =
|
if user = Accounts.get_user_by_email(email) do
|
||||||
Accounts.deliver_user_reset_password_instructions(
|
{:ok, _email} =
|
||||||
user,
|
Accounts.deliver_user_reset_password_instructions(
|
||||||
&url(~p"/users/reset-password/#{&1}")
|
user,
|
||||||
)
|
&url(~p"/users/reset-password/#{&1}")
|
||||||
end
|
)
|
||||||
|
end
|
||||||
|
|
||||||
# Always respond the same way whether or not the email exists — this is
|
# Always respond the same way whether or not the email exists — this is
|
||||||
# a deliberate anti-enumeration measure. If we redirected differently on
|
# a deliberate anti-enumeration measure. If we redirected differently on
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,12 @@ defmodule MicrowavepropWeb.UserSettingsController do
|
||||||
|
|
||||||
case Accounts.change_user_email(user, user_params) do
|
case Accounts.change_user_email(user, user_params) do
|
||||||
%{valid?: true} = changeset ->
|
%{valid?: true} = changeset ->
|
||||||
Accounts.deliver_user_update_email_instructions(
|
_ =
|
||||||
Ecto.Changeset.apply_action!(changeset, :insert),
|
Accounts.deliver_user_update_email_instructions(
|
||||||
user.email,
|
Ecto.Changeset.apply_action!(changeset, :insert),
|
||||||
&url(~p"/users/settings/confirm-email/#{&1}")
|
user.email,
|
||||||
)
|
&url(~p"/users/settings/confirm-email/#{&1}")
|
||||||
|
)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(
|
|> put_flash(
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ defmodule MicrowavepropWeb.Admin.ContactEditLive do
|
||||||
|
|
||||||
case Radio.approve_edit(edit, admin, note) do
|
case Radio.approve_edit(edit, admin, note) do
|
||||||
{:ok, approved} ->
|
{:ok, approved} ->
|
||||||
EditNotifier.deliver_edit_approved(approved)
|
_ = EditNotifier.deliver_edit_approved(approved)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|
@ -93,7 +93,7 @@ defmodule MicrowavepropWeb.Admin.ContactEditLive do
|
||||||
|
|
||||||
case Radio.reject_edit(edit, admin, note) do
|
case Radio.reject_edit(edit, admin, note) do
|
||||||
{:ok, rejected} ->
|
{:ok, rejected} ->
|
||||||
EditNotifier.deliver_edit_rejected(rejected)
|
_ = EditNotifier.deliver_edit_rejected(rejected)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket), do: Beacons.subscribe_beacons()
|
_ = if connected?(socket), do: Beacons.subscribe_beacons()
|
||||||
|
|
||||||
beacons = Beacons.list_beacons()
|
beacons = Beacons.list_beacons()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(%{"id" => id}, _session, socket) do
|
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)
|
beacon = Beacons.get_beacon!(id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
if connected?(socket) do
|
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)
|
kickoff_hydration(socket, contact)
|
||||||
else
|
else
|
||||||
|
|
@ -315,7 +315,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
if proposed == %{} do
|
if proposed == %{} do
|
||||||
put_flash(socket, :error, "No changes detected.")
|
put_flash(socket, :error, "No changes detected.")
|
||||||
else
|
else
|
||||||
Radio.apply_edit_to_contact(contact, proposed)
|
_ = Radio.apply_edit_to_contact(contact, proposed)
|
||||||
updated = Radio.get_contact!(contact.id)
|
updated = Radio.get_contact!(contact.id)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|
|
@ -387,7 +387,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
def handle_async(:solar, {:ok, solar}, socket) do
|
def handle_async(:solar, {:ok, solar}, socket) do
|
||||||
solar =
|
solar =
|
||||||
if socket.assigns.can_enqueue and is_nil(solar) do
|
if socket.assigns.can_enqueue and is_nil(solar) do
|
||||||
enqueue_missing_solar(socket.assigns.contact)
|
_ = enqueue_missing_solar(socket.assigns.contact)
|
||||||
nil
|
nil
|
||||||
else
|
else
|
||||||
solar
|
solar
|
||||||
|
|
@ -534,9 +534,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
has_data ->
|
has_data ->
|
||||||
if contact.weather_status != :complete do
|
_ =
|
||||||
Radio.set_enrichment_status!([contact.id], :weather_status, :complete)
|
if contact.weather_status != :complete do
|
||||||
end
|
Radio.set_enrichment_status!([contact.id], :weather_status, :complete)
|
||||||
|
end
|
||||||
|
|
||||||
%{contact | weather_status: :complete}
|
%{contact | weather_status: :complete}
|
||||||
|
|
||||||
|
|
@ -546,8 +547,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
if jobs == [] do
|
if jobs == [] do
|
||||||
%{contact | weather_status: :unavailable}
|
%{contact | weather_status: :unavailable}
|
||||||
else
|
else
|
||||||
Oban.insert_all(jobs)
|
_ = Oban.insert_all(jobs)
|
||||||
Radio.set_enrichment_status!([contact.id], :weather_status, :queued)
|
_ = Radio.set_enrichment_status!([contact.id], :weather_status, :queued)
|
||||||
%{contact | weather_status: :queued}
|
%{contact | weather_status: :queued}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -709,9 +710,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_enqueue_terrain(terrain, contact) when not is_nil(terrain) do
|
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)
|
if contact.terrain_status != :complete do
|
||||||
end
|
Radio.set_enrichment_status!([contact.id], :terrain_status, :complete)
|
||||||
|
end
|
||||||
|
|
||||||
%{contact | terrain_status: :complete}
|
%{contact | terrain_status: :complete}
|
||||||
end
|
end
|
||||||
|
|
@ -721,7 +723,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
contact.pos1 && contact.pos2 && contact.terrain_status != :queued ->
|
contact.pos1 && contact.pos2 && contact.terrain_status != :queued ->
|
||||||
case Oban.insert(TerrainProfileWorker.new(%{"contact_id" => contact.id})) do
|
case Oban.insert(TerrainProfileWorker.new(%{"contact_id" => contact.id})) do
|
||||||
{:ok, %{conflict?: false}} ->
|
{: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}
|
%{contact | terrain_status: :queued}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
@ -737,9 +739,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_enqueue_hrrr(hrrr, contact) when not is_nil(hrrr) do
|
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)
|
if contact.hrrr_status != :complete do
|
||||||
end
|
Radio.set_enrichment_status!([contact.id], :hrrr_status, :complete)
|
||||||
|
end
|
||||||
|
|
||||||
{hrrr, %{contact | hrrr_status: :complete}}
|
{hrrr, %{contact | hrrr_status: :complete}}
|
||||||
end
|
end
|
||||||
|
|
@ -765,9 +768,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
# Rust hrrr-point-worker picks them up alongside backfill work.
|
# Rust hrrr-point-worker picks them up alongside backfill work.
|
||||||
{:ok, _} = HrrrPointEnqueuer.enqueue(%{valid_time => [{rlat, rlon}]})
|
{:ok, _} = HrrrPointEnqueuer.enqueue(%{valid_time => [{rlat, rlon}]})
|
||||||
|
|
||||||
if contact.hrrr_status != :queued do
|
_ =
|
||||||
Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued)
|
if contact.hrrr_status != :queued do
|
||||||
end
|
Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued)
|
||||||
|
end
|
||||||
|
|
||||||
{nil, %{contact | hrrr_status: :queued}}
|
{nil, %{contact | hrrr_status: :queued}}
|
||||||
else
|
else
|
||||||
|
|
@ -785,15 +789,16 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
lon = contact.pos1 && contact.pos1["lon"]
|
lon = contact.pos1 && contact.pos1["lon"]
|
||||||
valid_time = NarrClient.snap_to_analysis_hour(contact.qso_timestamp)
|
valid_time = NarrClient.snap_to_analysis_hour(contact.qso_timestamp)
|
||||||
|
|
||||||
if lat && lon && NarrClient.in_coverage?(valid_time) do
|
_ =
|
||||||
%{
|
if lat && lon && NarrClient.in_coverage?(valid_time) do
|
||||||
"lat" => lat,
|
%{
|
||||||
"lon" => lon,
|
"lat" => lat,
|
||||||
"valid_time" => DateTime.to_iso8601(valid_time)
|
"lon" => lon,
|
||||||
}
|
"valid_time" => DateTime.to_iso8601(valid_time)
|
||||||
|> NarrFetchWorker.new()
|
}
|
||||||
|> Oban.insert()
|
|> NarrFetchWorker.new()
|
||||||
end
|
|> Oban.insert()
|
||||||
|
end
|
||||||
|
|
||||||
contact
|
contact
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,10 @@ defmodule MicrowavepropWeb.ImportLive do
|
||||||
|> push_navigate(to: ~p"/submit")}
|
|> push_navigate(to: ~p"/submit")}
|
||||||
|
|
||||||
%ImportRun{} = run ->
|
%ImportRun{} = run ->
|
||||||
if connected?(socket) do
|
_ =
|
||||||
Phoenix.PubSub.subscribe(@pubsub, "csv_import:#{run.id}")
|
if connected?(socket) do
|
||||||
end
|
Phoenix.PubSub.subscribe(@pubsub, "csv_import:#{run.id}")
|
||||||
|
end
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,13 @@ defmodule MicrowavepropWeb.MapLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(params, session, socket) do
|
def mount(params, session, socket) do
|
||||||
if connected?(socket) do
|
_ =
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
if connected?(socket) do
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
|
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
||||||
Process.send_after(self(), :refresh_pipeline_status, @pipeline_status_refresh_ms)
|
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
|
||||||
Process.send_after(self(), :advance_now_cursor, @advance_now_cursor_ms)
|
_ = Process.send_after(self(), :refresh_pipeline_status, @pipeline_status_refresh_ms)
|
||||||
end
|
_ = Process.send_after(self(), :advance_now_cursor, @advance_now_cursor_ms)
|
||||||
|
end
|
||||||
|
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,10 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket) do
|
_ =
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
if connected?(socket) do
|
||||||
end
|
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
||||||
|
end
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,10 @@ defmodule MicrowavepropWeb.RoverLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket) do
|
_ =
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
if connected?(socket) do
|
||||||
end
|
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
||||||
|
end
|
||||||
|
|
||||||
initial_scores = Propagation.latest_scores(@default_band, @initial_bounds)
|
initial_scores = Propagation.latest_scores(@default_band, @initial_bounds)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,16 @@ defmodule MicrowavepropWeb.StatusLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket) do
|
_ =
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
|
if connected?(socket) do
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
|
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:contact_status")
|
||||||
# Rust prop-grid-rs emits NOTIFY propagation_ready on completion —
|
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "db:oban_jobs")
|
||||||
# PropagationNotifyListener fans it out as `propagation:updated` so
|
# Rust prop-grid-rs emits NOTIFY propagation_ready on completion —
|
||||||
# the grid_tasks panel transitions from "running" → "done" without
|
# PropagationNotifyListener fans it out as `propagation:updated` so
|
||||||
# a manual refresh.
|
# the grid_tasks panel transitions from "running" → "done" without
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
# a manual refresh.
|
||||||
end
|
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:updated")
|
||||||
|
end
|
||||||
|
|
||||||
stats = fetch_stats()
|
stats = fetch_stats()
|
||||||
unprocessed = count_unprocessed()
|
unprocessed = count_unprocessed()
|
||||||
|
|
|
||||||
|
|
@ -138,10 +138,11 @@ defmodule MicrowavepropWeb.WeatherMapLive do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def mount(_params, _session, socket) do
|
def mount(_params, _session, socket) do
|
||||||
if connected?(socket) do
|
_ =
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "weather:updated")
|
if connected?(socket) do
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
|
:ok = Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "weather:updated")
|
||||||
end
|
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "propagation:pipeline")
|
||||||
|
end
|
||||||
|
|
||||||
# Default the timeline cursor to the valid_time closest to "now" so
|
# Default the timeline cursor to the valid_time closest to "now" so
|
||||||
# users land on current conditions, not a +11h forecast hour (which
|
# users land on current conditions, not a +11h forecast hour (which
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,10 @@ defmodule MicrowavepropWeb.UserAuth do
|
||||||
user_token = get_session(conn, :user_token)
|
user_token = get_session(conn, :user_token)
|
||||||
user_token && Accounts.delete_user_session_token(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", %{})
|
if live_socket_id = get_session(conn, :live_socket_id) do
|
||||||
end
|
MicrowavepropWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
|
||||||
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> renew_session(nil)
|
|> renew_session(nil)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ defmodule Mix.Tasks.Backtest do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(argv) do
|
def run(argv) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} =
|
{opts, _, _} =
|
||||||
OptionParser.parse(argv,
|
OptionParser.parse(argv,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ defmodule Mix.Tasks.HrrrBackfill do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(args) do
|
def run(args) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} = OptionParser.parse(args, switches: [all: :boolean, limit: :integer])
|
{opts, _, _} = OptionParser.parse(args, switches: [all: :boolean, limit: :integer])
|
||||||
refetch_all? = Keyword.get(opts, :all, false)
|
refetch_all? = Keyword.get(opts, :all, false)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ defmodule Mix.Tasks.HrrrClimatology do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(argv) do
|
def run(argv) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} = OptionParser.parse(argv, switches: [min_samples: :integer])
|
{opts, _, _} = OptionParser.parse(argv, switches: [min_samples: :integer])
|
||||||
min_samples = Keyword.get(opts, :min_samples, 3)
|
min_samples = Keyword.get(opts, :min_samples, 3)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ defmodule Mix.Tasks.HrrrNativeBackfill do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(argv) do
|
def run(argv) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
|
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
|
||||||
limit = Keyword.get(opts, :limit, 50)
|
limit = Keyword.get(opts, :limit, 50)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ defmodule Mix.Tasks.HrrrNativeDeriveFields do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(argv) do
|
def run(argv) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
|
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
|
||||||
limit = Keyword.get(opts, :limit, 10_000)
|
limit = Keyword.get(opts, :limit, 10_000)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ defmodule Mix.Tasks.ImportContestLogs do
|
||||||
end
|
end
|
||||||
|
|
||||||
Mix.Task.run("app.start")
|
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)
|
# Concatenate all CSVs into one string (skip headers after the first file)
|
||||||
combined =
|
combined =
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ defmodule Mix.Tasks.NexradBackfill do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(argv) do
|
def run(argv) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
|
{opts, _, _} = OptionParser.parse(argv, switches: [limit: :integer])
|
||||||
limit = Keyword.get(opts, :limit, 200)
|
limit = Keyword.get(opts, :limit, 200)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ defmodule Mix.Tasks.PropagationGrid do
|
||||||
|
|
||||||
IO.puts("Starting propagation grid computation...")
|
IO.puts("Starting propagation grid computation...")
|
||||||
|
|
||||||
PropagationGridWorker.perform(%Oban.Job{args: %{}})
|
_ = PropagationGridWorker.perform(%Oban.Job{args: %{}})
|
||||||
IO.puts("Done!")
|
IO.puts("Done!")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ defmodule Mix.Tasks.RadarBackfill do
|
||||||
|> Enum.with_index(1)
|
|> Enum.with_index(1)
|
||||||
|> Enum.each(fn {batch, i} ->
|
|> Enum.each(fn {batch, i} ->
|
||||||
jobs = Enum.map(batch, &CommonVolumeRadarWorker.new(%{"contact_id" => &1}))
|
jobs = Enum.map(batch, &CommonVolumeRadarWorker.new(%{"contact_id" => &1}))
|
||||||
Oban.insert_all(jobs)
|
_ = Oban.insert_all(jobs)
|
||||||
Radio.set_enrichment_status!(batch, :radar_status, :queued)
|
_ = Radio.set_enrichment_status!(batch, :radar_status, :queued)
|
||||||
Mix.shell().info(" batch #{i}: #{length(batch)} enqueued")
|
Mix.shell().info(" batch #{i}: #{length(batch)} enqueued")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ defmodule Mix.Tasks.RecalibrateScorer do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(argv) do
|
def run(argv) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{opts, _, _} =
|
{opts, _, _} =
|
||||||
OptionParser.parse(argv,
|
OptionParser.parse(argv,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ defmodule Mix.Tasks.ResetEnrichment do
|
||||||
@impl Mix.Task
|
@impl Mix.Task
|
||||||
def run(_args) do
|
def run(_args) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
Oban.pause_all_queues(Oban)
|
_ = Oban.pause_all_queues(Oban)
|
||||||
|
|
||||||
{count, _} =
|
{count, _} =
|
||||||
Contact
|
Contact
|
||||||
|
|
|
||||||
|
|
@ -239,13 +239,14 @@ defmodule Mix.Tasks.PropagationAnalyze do
|
||||||
end)
|
end)
|
||||||
|> Enum.sort_by(fn {_, rho, _} -> -abs(rho || 0) 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"
|
for {label, rho, n} <- correlations do
|
||||||
|
rho_str = if rho, do: format_num(rho, 4), else: "N/A"
|
||||||
|
|
||||||
IO.puts(
|
IO.puts(
|
||||||
" #{String.pad_trailing(label, 28)} #{String.pad_leading(rho_str, 8)} #{String.pad_leading(Integer.to_string(n), 8)}"
|
" #{String.pad_trailing(label, 28)} #{String.pad_leading(rho_str, 8)} #{String.pad_leading(Integer.to_string(n), 8)}"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.puts("")
|
IO.puts("")
|
||||||
|
|
@ -353,11 +354,12 @@ defmodule Mix.Tasks.PropagationAnalyze do
|
||||||
|
|
||||||
IO.puts("\n#{format_band(band)} (n=#{length(band_data)}):")
|
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)
|
for {key, label, bins} <- factor_configs do
|
||||||
end
|
_ = print_binned_factor(band_data, key, label, bins)
|
||||||
|
end
|
||||||
|
|
||||||
print_categorical_factor(band_data, terrain_config)
|
_ = print_categorical_factor(band_data, terrain_config)
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.puts("")
|
IO.puts("")
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ defmodule Mix.Tasks.PropagationTrain do
|
||||||
Model.save(trained_state)
|
Model.save(trained_state)
|
||||||
IO.puts("Done.")
|
IO.puts("Done.")
|
||||||
|
|
||||||
check_monthly_bias(trained_state)
|
_ = check_monthly_bias(trained_state)
|
||||||
|
|
||||||
"-" |> String.duplicate(70) |> IO.puts()
|
"-" |> String.duplicate(70) |> IO.puts()
|
||||||
IO.puts("Finished: #{DateTime.to_string(DateTime.utc_now())}")
|
IO.puts("Finished: #{DateTime.to_string(DateTime.utc_now())}")
|
||||||
|
|
|
||||||
3
mix.exs
3
mix.exs
|
|
@ -21,7 +21,8 @@ defmodule Microwaveprop.MixProject do
|
||||||
:unmatched_returns,
|
:unmatched_returns,
|
||||||
:extra_return,
|
:extra_return,
|
||||||
:missing_return
|
:missing_return
|
||||||
]
|
],
|
||||||
|
ignore_warnings: ".dialyzer_ignore.exs"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue