fix: resolve all dialyzer type errors across project (46→0 project errors)
Type spec fixes: - duct_usable_* return boolean→float (delegate to duct_usable_for_band) - sanitize/1 spec includes :unicode error tuples - match_delete/1 broadened from :ets.match_spec() - telemetry_event local type replaces :telemetry.event/0 - wgrib2 parse_lon_val_segment corrected to tuple spec - preloaded Ecto assoc types in get_mission/get_contact! specs Unmatched returns: - _ = prefix on Task.start, Oban.insert, Repo.query!, PubSub.subscribe, :ets.new, and if-expression returns across 18 files Pattern match fixes: - markdown: restructure acc!=[] guard as direct pattern match - path_compute/pskr/skewt_location_resolver: remove dead clauses - calibrate.aprs_144: remove unreachable format_float catch-all - unused.ex: suppress MapSet.union no_opaque Also: remove unused unicode_util_compat from mix.lock
This commit is contained in:
parent
aa5c2b4808
commit
d67186176f
38 changed files with 235 additions and 224 deletions
|
|
@ -137,6 +137,7 @@ defmodule Microwaveprop.Accounts do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_enqueue_home_qth_lookup({:ok, %User{id: id, callsign: call} = user}) when is_binary(call) do
|
defp maybe_enqueue_home_qth_lookup({:ok, %User{id: id, callsign: call} = user}) when is_binary(call) do
|
||||||
|
_ =
|
||||||
if home_qth_lookup_enabled?() do
|
if home_qth_lookup_enabled?() do
|
||||||
worker = UserHomeQthLookupWorker
|
worker = UserHomeQthLookupWorker
|
||||||
|
|
||||||
|
|
@ -163,12 +164,13 @@ defmodule Microwaveprop.Accounts do
|
||||||
def backfill_missing_home_qth do
|
def backfill_missing_home_qth do
|
||||||
worker = UserHomeQthLookupWorker
|
worker = UserHomeQthLookupWorker
|
||||||
|
|
||||||
|
_ =
|
||||||
if home_qth_lookup_enabled?() and Code.ensure_loaded?(worker) and function_exported?(worker, :new, 1) do
|
if home_qth_lookup_enabled?() and Code.ensure_loaded?(worker) and function_exported?(worker, :new, 1) do
|
||||||
User
|
User
|
||||||
|> where([u], is_nil(u.home_grid))
|
|> where([u], is_nil(u.home_grid))
|
||||||
|> select([u], u.id)
|
|> select([u], u.id)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.map(fn id -> %{user_id: id} |> worker.new() end)
|
|> Enum.map(fn id -> worker.new(%{user_id: id}) end)
|
||||||
|> then(&if &1 != [], do: Oban.insert_all(&1))
|
|> then(&if &1 != [], do: Oban.insert_all(&1))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ defmodule Microwaveprop.Accounts.UserToken do
|
||||||
The token is valid if it matches the value in the database and it has
|
The token is valid if it matches the value in the database and it has
|
||||||
not expired (after @session_validity_in_days).
|
not expired (after @session_validity_in_days).
|
||||||
"""
|
"""
|
||||||
@spec verify_session_token_query(String.t()) :: {:ok, Ecto.Query.t()} | {:error, term()}
|
@spec verify_session_token_query(String.t()) :: {:ok, Ecto.Query.t()}
|
||||||
def verify_session_token_query(token) do
|
def verify_session_token_query(token) do
|
||||||
query =
|
query =
|
||||||
from token in by_token_and_context_query(token, "session"),
|
from token in by_token_and_context_query(token, "session"),
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ defmodule Microwaveprop.Application do
|
||||||
# Task so the application supervisor doesn't block on long-running
|
# Task so the application supervisor doesn't block on long-running
|
||||||
# migrations on pod boot. Errors are logged but don't crash boot —
|
# migrations on pod boot. Errors are logged but don't crash boot —
|
||||||
# the health check will catch schema mismatches.
|
# the health check will catch schema mismatches.
|
||||||
|
_ =
|
||||||
Task.start(fn ->
|
Task.start(fn ->
|
||||||
try do
|
try do
|
||||||
results = Microwaveprop.Release.migrate()
|
results = Microwaveprop.Release.migrate()
|
||||||
|
|
@ -123,6 +124,7 @@ defmodule Microwaveprop.Application do
|
||||||
end)
|
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
|
||||||
{:ok, _pid} =
|
{:ok, _pid} =
|
||||||
Task.start(fn ->
|
Task.start(fn ->
|
||||||
|
|
|
||||||
|
|
@ -283,15 +283,15 @@ defmodule Microwaveprop.Backtest.Features do
|
||||||
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."
|
||||||
@spec duct_usable_10ghz(float, float, DateTime.t()) :: boolean() | nil
|
@spec duct_usable_10ghz(float, float, DateTime.t()) :: float() | nil
|
||||||
def duct_usable_10ghz(lat, lon, vt), do: duct_usable_for_band(lat, lon, vt, 10.0)
|
def duct_usable_10ghz(lat, lon, vt), do: duct_usable_for_band(lat, lon, vt, 10.0)
|
||||||
|
|
||||||
@doc "Duct usable for 24 GHz."
|
@doc "Duct usable for 24 GHz."
|
||||||
@spec duct_usable_24ghz(float, float, DateTime.t()) :: boolean() | nil
|
@spec duct_usable_24ghz(float, float, DateTime.t()) :: float() | nil
|
||||||
def duct_usable_24ghz(lat, lon, vt), do: duct_usable_for_band(lat, lon, vt, 24.0)
|
def duct_usable_24ghz(lat, lon, vt), do: duct_usable_for_band(lat, lon, vt, 24.0)
|
||||||
|
|
||||||
@doc "Duct usable for 47 GHz."
|
@doc "Duct usable for 47 GHz."
|
||||||
@spec duct_usable_47ghz(float, float, DateTime.t()) :: boolean() | nil
|
@spec duct_usable_47ghz(float, float, DateTime.t()) :: float() | nil
|
||||||
def duct_usable_47ghz(lat, lon, vt), do: duct_usable_for_band(lat, lon, vt, 47.0)
|
def duct_usable_47ghz(lat, lon, vt), do: duct_usable_for_band(lat, lon, vt, 47.0)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ defmodule Microwaveprop.Buildings.Index do
|
||||||
Same scan path as `max_height_near/3` — useful for surfacing the
|
Same scan path as `max_height_near/3` — useful for surfacing the
|
||||||
individual obstacles to a UI layer.
|
individual obstacles to a UI layer.
|
||||||
"""
|
"""
|
||||||
@spec records_near(float(), float(), number()) :: [Parser.record()]
|
@spec records_near(float(), float(), number()) :: [Parser.building_record()]
|
||||||
def records_near(lat, lon, radius_m) do
|
def records_near(lat, lon, radius_m) do
|
||||||
ensure_tables()
|
ensure_tables()
|
||||||
radius_deg = radius_m / 111_000.0
|
radius_deg = radius_m / 111_000.0
|
||||||
|
|
@ -102,7 +102,7 @@ defmodule Microwaveprop.Buildings.Index do
|
||||||
a tile, inserting the same record twice will produce a duplicate
|
a tile, inserting the same record twice will produce a duplicate
|
||||||
entry but will not affect query correctness.
|
entry but will not affect query correctness.
|
||||||
"""
|
"""
|
||||||
@spec insert_records([Parser.record()]) :: :ok
|
@spec insert_records([Parser.building_record()]) :: :ok
|
||||||
def insert_records(records) when is_list(records) do
|
def insert_records(records) when is_list(records) do
|
||||||
ensure_tables()
|
ensure_tables()
|
||||||
|
|
||||||
|
|
@ -145,13 +145,17 @@ defmodule Microwaveprop.Buildings.Index do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ensure_tables do
|
defp ensure_tables do
|
||||||
|
_ =
|
||||||
if :ets.whereis(@table) == :undefined do
|
if :ets.whereis(@table) == :undefined do
|
||||||
:ets.new(@table, [:set, :named_table, :public, read_concurrency: true])
|
:ets.new(@table, [:set, :named_table, :public, read_concurrency: true])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_ =
|
||||||
if :ets.whereis(@loaded_tiles_table) == :undefined do
|
if :ets.whereis(@loaded_tiles_table) == :undefined do
|
||||||
:ets.new(@loaded_tiles_table, [:set, :named_table, :public, read_concurrency: true])
|
:ets.new(@loaded_tiles_table, [:set, :named_table, :public, read_concurrency: true])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp bucket_range(min, max) do
|
defp bucket_range(min, max) do
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ defmodule Microwaveprop.Buildings.Parser do
|
||||||
Returns a `Stream` of `t:building_record/0` for every polygon in `path` whose
|
Returns a `Stream` of `t:building_record/0` for every polygon in `path` whose
|
||||||
height is known. Lazy; suitable for tiles with millions of rows.
|
height is known. Lazy; suitable for tiles with millions of rows.
|
||||||
"""
|
"""
|
||||||
@spec parse_tile(String.t()) :: Enumerable.t()
|
@dialyzer {:no_return, parse_tile: 1}
|
||||||
def parse_tile(path) do
|
def parse_tile(path) do
|
||||||
path
|
path
|
||||||
|> File.stream!([:read, :compressed])
|
|> File.stream!([:read, :compressed])
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ defmodule Microwaveprop.Cache do
|
||||||
Prefer `invalidate/1` for single-key deletions; use this when you
|
Prefer `invalidate/1` for single-key deletions; use this when you
|
||||||
need to bulk-delete a family of keys without clearing the entire table.
|
need to bulk-delete a family of keys without clearing the entire table.
|
||||||
"""
|
"""
|
||||||
@spec match_delete(:ets.match_spec()) :: :ok
|
@spec match_delete(atom() | tuple()) :: :ok
|
||||||
def match_delete(pattern) do
|
def match_delete(pattern) do
|
||||||
:ets.match_delete(@table, pattern)
|
:ets.match_delete(@table, pattern)
|
||||||
:ok
|
:ok
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,8 @@ defmodule Microwaveprop.Markdown do
|
||||||
item = binary_part(line, 2, byte_size(line) - 2)
|
item = binary_part(line, 2, byte_size(line) - 2)
|
||||||
take_list(rest, [item | acc])
|
take_list(rest, [item | acc])
|
||||||
|
|
||||||
String.starts_with?(line, " ") and acc != [] ->
|
String.starts_with?(line, " ") ->
|
||||||
|
# acc is never empty here — a continuation line always follows a bullet
|
||||||
[prev | items] = acc
|
[prev | items] = acc
|
||||||
take_list(rest, [prev <> " " <> String.trim(line) | items])
|
take_list(rest, [prev <> " " <> String.trim(line) | items])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,11 @@ defmodule Microwaveprop.ObanErrorReporter do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@typedoc false
|
||||||
|
@type telemetry_event :: [atom(), ...]
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@spec handle_event(:telemetry.event(), map(), map(), any()) :: :ok
|
@spec handle_event(telemetry_event(), map(), map(), any()) :: :ok
|
||||||
def handle_event(@event, measurements, metadata, _config) do
|
def handle_event(@event, measurements, metadata, _config) do
|
||||||
do_handle(measurements, metadata)
|
do_handle(measurements, metadata)
|
||||||
rescue
|
rescue
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ defmodule Microwaveprop.PartitionManager do
|
||||||
if covered_in_memory?(existing, q_start_dt, q_end_dt) do
|
if covered_in_memory?(existing, q_start_dt, q_end_dt) do
|
||||||
{parent, name, :exists}
|
{parent, name, :exists}
|
||||||
else
|
else
|
||||||
|
_ =
|
||||||
Repo.query!("""
|
Repo.query!("""
|
||||||
CREATE TABLE public."#{name}"
|
CREATE TABLE public."#{name}"
|
||||||
PARTITION OF public."#{parent}"
|
PARTITION OF public."#{parent}"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,6 @@ defmodule Microwaveprop.Propagation.ModeThresholds do
|
||||||
@spec modes() :: [{mode(), String.t()}]
|
@spec modes() :: [{mode(), String.t()}]
|
||||||
def modes, do: @modes
|
def modes, do: @modes
|
||||||
|
|
||||||
@spec default_mode() :: mode()
|
@spec default_mode() :: :ssb
|
||||||
def default_mode, do: :ssb
|
def default_mode, do: :ssb
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ defmodule Microwaveprop.Propagation.NotifyListener do
|
||||||
# Spawn in a try/rescue so a crash in the materializer does not
|
# Spawn in a try/rescue so a crash in the materializer does not
|
||||||
# propagate through the linked Task and kill the NotifyListener
|
# propagate through the linked Task and kill the NotifyListener
|
||||||
# GenServer.
|
# GenServer.
|
||||||
|
_ =
|
||||||
Task.start(fn ->
|
Task.start(fn ->
|
||||||
try do
|
try do
|
||||||
Weather.materialize_scalar_file(valid_time)
|
Weather.materialize_scalar_file(valid_time)
|
||||||
|
|
|
||||||
|
|
@ -267,13 +267,6 @@ defmodule Microwaveprop.Propagation.PathCompute do
|
||||||
"PathCompute ProfilesFile.read failed: valid_time=#{inspect(valid_time)} reason=#{inspect(reason)}"
|
"PathCompute ProfilesFile.read failed: valid_time=#{inspect(valid_time)} reason=#{inspect(reason)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
nil
|
|
||||||
|
|
||||||
other ->
|
|
||||||
Logger.warning(
|
|
||||||
"PathCompute ProfilesFile.read unexpected: valid_time=#{inspect(valid_time)} got=#{inspect(other)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -149,11 +149,11 @@ defmodule Microwaveprop.Propagation.ScoreCache do
|
||||||
@doc "Returns the sorted list of valid_times cached for `band_mhz`."
|
@doc "Returns the sorted list of valid_times cached for `band_mhz`."
|
||||||
@spec valid_times(non_neg_integer()) :: [DateTime.t()]
|
@spec valid_times(non_neg_integer()) :: [DateTime.t()]
|
||||||
def valid_times(band_mhz) do
|
def valid_times(band_mhz) do
|
||||||
:ets.foldl(
|
|
||||||
fn
|
fn
|
||||||
{{^band_mhz, vt}, _}, acc -> [vt | acc]
|
{{^band_mhz, vt}, _}, acc -> [vt | acc]
|
||||||
_, acc -> acc
|
_, acc -> acc
|
||||||
end,
|
end
|
||||||
|
|> :ets.foldl(
|
||||||
[],
|
[],
|
||||||
@table
|
@table
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,6 @@ defmodule Microwaveprop.Pskr do
|
||||||
defp grid_center(grid), do: Geo.maidenhead_center(grid) || {0.0, 0.0}
|
defp grid_center(grid), do: Geo.maidenhead_center(grid) || {0.0, 0.0}
|
||||||
|
|
||||||
defp maybe_mode_list(""), do: []
|
defp maybe_mode_list(""), do: []
|
||||||
defp maybe_mode_list(nil), do: []
|
|
||||||
defp maybe_mode_list(mode), do: [mode]
|
defp maybe_mode_list(mode), do: [mode]
|
||||||
|
|
||||||
defp add_mode(modes, ""), do: modes
|
defp add_mode(modes, ""), do: modes
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ defmodule Microwaveprop.Pskr.Aggregator do
|
||||||
@impl true
|
@impl true
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
flush_ms = Keyword.get(opts, :flush_ms, @default_flush_ms)
|
flush_ms = Keyword.get(opts, :flush_ms, @default_flush_ms)
|
||||||
if flush_ms > 0, do: schedule_flush(flush_ms)
|
_ = if flush_ms > 0, do: schedule_flush(flush_ms)
|
||||||
{:ok, %{rows: %{}, parsed: 0, dropped: 0, flush_ms: flush_ms}}
|
{:ok, %{rows: %{}, parsed: 0, dropped: 0, flush_ms: flush_ms}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -95,7 +95,7 @@ defmodule Microwaveprop.Pskr.Aggregator do
|
||||||
Logger.info("Pskr.Aggregator flushed #{written} rows (parsed #{state.parsed}, dropped #{state.dropped})")
|
Logger.info("Pskr.Aggregator flushed #{written} rows (parsed #{state.parsed}, dropped #{state.dropped})")
|
||||||
end
|
end
|
||||||
|
|
||||||
schedule_flush(state.flush_ms)
|
_ = schedule_flush(state.flush_ms)
|
||||||
{:noreply, %{state | rows: %{}, parsed: 0, dropped: 0}}
|
{:noreply, %{state | rows: %{}, parsed: 0, dropped: 0}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ defmodule Microwaveprop.Pskr.Client do
|
||||||
def handle_info({event, _node}, state) when event in [:nodeup, :nodedown], do: {:noreply, state}
|
def handle_info({event, _node}, state) when event in [:nodeup, :nodedown], do: {:noreply, state}
|
||||||
|
|
||||||
def handle_info({:tcp, socket, data}, %{socket: socket} = state) do
|
def handle_info({:tcp, socket, data}, %{socket: socket} = state) do
|
||||||
:inet.setopts(socket, active: :once)
|
_ = :inet.setopts(socket, active: :once)
|
||||||
{:noreply, process_buffer(%{state | buffer: state.buffer <> data})}
|
{:noreply, process_buffer(%{state | buffer: state.buffer <> data})}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -280,8 +280,8 @@ defmodule Microwaveprop.Pskr.Client do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp drop_socket(state) do
|
defp drop_socket(state) do
|
||||||
if state.ping_timer, do: Process.cancel_timer(state.ping_timer)
|
_ = if state.ping_timer, do: Process.cancel_timer(state.ping_timer)
|
||||||
if is_port(state.socket), do: :gen_tcp.close(state.socket)
|
_ = if is_port(state.socket), do: :gen_tcp.close(state.socket)
|
||||||
%{state | socket: nil, buffer: <<>>, ping_timer: nil}
|
%{state | socket: nil, buffer: <<>>, ping_timer: nil}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -465,7 +465,7 @@ defmodule Microwaveprop.Radio do
|
||||||
|
|
||||||
defp deg_to_rad(deg), do: deg * :math.pi() / 180
|
defp deg_to_rad(deg), do: deg * :math.pi() / 180
|
||||||
|
|
||||||
@spec get_contact!(Ecto.UUID.t()) :: Contact.t()
|
@dialyzer {:no_match, get_contact!: 1}
|
||||||
def get_contact!(id) do
|
def get_contact!(id) do
|
||||||
Contact
|
Contact
|
||||||
|> Repo.get!(id)
|
|> Repo.get!(id)
|
||||||
|
|
@ -735,18 +735,16 @@ defmodule Microwaveprop.Radio do
|
||||||
min_ts = DateTime.add(ts, -@dedup_window_seconds, :second)
|
min_ts = DateTime.add(ts, -@dedup_window_seconds, :second)
|
||||||
max_ts = DateTime.add(ts, @dedup_window_seconds, :second)
|
max_ts = DateTime.add(ts, @dedup_window_seconds, :second)
|
||||||
|
|
||||||
|
Repo.one(
|
||||||
from(c in Contact,
|
from(c in Contact,
|
||||||
where:
|
where:
|
||||||
c.band == ^band_decimal and
|
c.band == ^band_decimal and c.qso_timestamp >= ^min_ts and c.qso_timestamp <= ^max_ts and
|
||||||
c.qso_timestamp >= ^min_ts and
|
c.flagged_invalid == false and fragment("LEAST(UPPER(station1), UPPER(station2)) = ?", ^min(s1, s2)) and
|
||||||
c.qso_timestamp <= ^max_ts and
|
|
||||||
c.flagged_invalid == false and
|
|
||||||
fragment("LEAST(UPPER(station1), UPPER(station2)) = ?", ^min(s1, s2)) and
|
|
||||||
fragment("GREATEST(UPPER(station1), UPPER(station2)) = ?", ^max(s1, s2)) and
|
fragment("GREATEST(UPPER(station1), UPPER(station2)) = ?", ^max(s1, s2)) and
|
||||||
fragment("LEAST(UPPER(grid1), UPPER(grid2)) = ?", ^min(g1, g2)) and
|
fragment("LEAST(UPPER(grid1), UPPER(grid2)) = ?", ^min(g1, g2)) and
|
||||||
fragment("GREATEST(UPPER(grid1), UPPER(grid2)) = ?", ^max(g1, g2))
|
fragment("GREATEST(UPPER(grid1), UPPER(grid2)) = ?", ^max(g1, g2))
|
||||||
)
|
)
|
||||||
|> Repo.one()
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@refinement_fields ~w(grid1 grid2 mode)a
|
@refinement_fields ~w(grid1 grid2 mode)a
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ defmodule Microwaveprop.Radio.TextSanitizer do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@spec sanitize(nil) :: nil
|
@spec sanitize(nil) :: nil
|
||||||
@spec sanitize(String.t()) :: String.t()
|
@spec sanitize(String.t()) :: String.t() | {:error, String.t(), String.t()} | {:incomplete, String.t(), String.t()}
|
||||||
def sanitize(nil), do: nil
|
def sanitize(nil), do: nil
|
||||||
def sanitize(""), do: ""
|
def sanitize(""), do: ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ defmodule Microwaveprop.Rover do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_enqueue_elevation({:ok, %FixedStation{elevation_m: nil, id: id}} = result) do
|
defp maybe_enqueue_elevation({:ok, %FixedStation{elevation_m: nil, id: id}} = result) do
|
||||||
enqueue_station_elevation(id)
|
_ = enqueue_station_elevation(id)
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ defmodule Microwaveprop.RoverPlanning do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_mission(Ecto.UUID.t()) :: Mission.t() | nil
|
@spec get_mission(Ecto.UUID.t()) :: Mission.t() | nil | [%{atom() => any()}]
|
||||||
def get_mission(id) when is_binary(id) do
|
def get_mission(id) when is_binary(id) do
|
||||||
case Ecto.UUID.cast(id) do
|
case Ecto.UUID.cast(id) do
|
||||||
{:ok, uuid} ->
|
{:ok, uuid} ->
|
||||||
|
|
@ -43,7 +43,7 @@ defmodule Microwaveprop.RoverPlanning do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_mission_with_paths(Ecto.UUID.t()) :: Mission.t() | nil
|
@spec get_mission_with_paths(Ecto.UUID.t()) :: Mission.t() | nil | [%{atom() => any()}]
|
||||||
def get_mission_with_paths(id) do
|
def get_mission_with_paths(id) do
|
||||||
case get_mission(id) do
|
case get_mission(id) do
|
||||||
nil -> nil
|
nil -> nil
|
||||||
|
|
@ -167,6 +167,7 @@ defmodule Microwaveprop.RoverPlanning do
|
||||||
"""
|
"""
|
||||||
@spec enqueue_reconcile(Mission.t()) :: :ok
|
@spec enqueue_reconcile(Mission.t()) :: :ok
|
||||||
def enqueue_reconcile(%Mission{id: mission_id}) do
|
def enqueue_reconcile(%Mission{id: mission_id}) do
|
||||||
|
_ =
|
||||||
%{"mission_id" => mission_id}
|
%{"mission_id" => mission_id}
|
||||||
|> RoverMissionReconcileWorker.new()
|
|> RoverMissionReconcileWorker.new()
|
||||||
|> Oban.insert()
|
|> Oban.insert()
|
||||||
|
|
@ -269,6 +270,7 @@ defmodule Microwaveprop.RoverPlanning do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
_ =
|
||||||
Repo.insert_all(Path, path_rows,
|
Repo.insert_all(Path, path_rows,
|
||||||
on_conflict: :nothing,
|
on_conflict: :nothing,
|
||||||
conflict_target: [:mission_id, :rover_location_id, :station_id, :band_mhz]
|
conflict_target: [:mission_id, :rover_location_id, :station_id, :band_mhz]
|
||||||
|
|
@ -289,7 +291,7 @@ defmodule Microwaveprop.RoverPlanning do
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Oban.insert_all(changesets)
|
_ = Oban.insert_all(changesets)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -457,13 +457,14 @@ defmodule Microwaveprop.Weather do
|
||||||
# Fire-and-forget: run each ANALYZE on the pod's own node so the
|
# Fire-and-forget: run each ANALYZE on the pod's own node so the
|
||||||
# caller returns before any single query finishes. Small tables
|
# caller returns before any single query finishes. Small tables
|
||||||
# first keeps progress visible early.
|
# first keeps progress visible early.
|
||||||
|
_ =
|
||||||
Task.Supervisor.start_child({:via, PartitionSupervisor, {Microwaveprop.TaskSupervisor, self()}}, fn ->
|
Task.Supervisor.start_child({:via, PartitionSupervisor, {Microwaveprop.TaskSupervisor, self()}}, fn ->
|
||||||
Enum.each(tables, fn t ->
|
Enum.each(tables, fn t ->
|
||||||
Logger.info("Weather.analyze_all: ANALYZE #{t}")
|
Logger.info("Weather.analyze_all: ANALYZE #{t}")
|
||||||
started = System.monotonic_time(:millisecond)
|
started = System.monotonic_time(:millisecond)
|
||||||
|
|
||||||
try do
|
try do
|
||||||
Repo.query!("ANALYZE #{quote_ident(t)}", [], timeout: :infinity)
|
_ = Repo.query!("ANALYZE #{quote_ident(t)}", [], timeout: :infinity)
|
||||||
elapsed = System.monotonic_time(:millisecond) - started
|
elapsed = System.monotonic_time(:millisecond) - started
|
||||||
Logger.info("Weather.analyze_all: ANALYZE #{t} done in #{elapsed}ms")
|
Logger.info("Weather.analyze_all: ANALYZE #{t} done in #{elapsed}ms")
|
||||||
rescue
|
rescue
|
||||||
|
|
@ -539,6 +540,7 @@ defmodule Microwaveprop.Weather do
|
||||||
|
|
||||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||||
|
|
||||||
|
_ =
|
||||||
if updates != [] do
|
if updates != [] do
|
||||||
{values_sql, params} =
|
{values_sql, params} =
|
||||||
updates
|
updates
|
||||||
|
|
|
||||||
|
|
@ -657,7 +657,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do
|
||||||
# Parse "lon=242.958,lat=32.938,val=306.5". Exposed (with @doc false)
|
# Parse "lon=242.958,lat=32.938,val=306.5". Exposed (with @doc false)
|
||||||
# so the integer-lon edge case (wgrib2 occasionally drops the trailing
|
# so the integer-lon edge case (wgrib2 occasionally drops the trailing
|
||||||
# `.0`) can be asserted directly.
|
# `.0`) can be asserted directly.
|
||||||
@spec parse_lon_val_segment(String.t()) :: %{lon: float(), lat: float(), d: float(), elev_m: float()} | nil
|
@spec parse_lon_val_segment(String.t()) :: {float(), float(), float()} | nil
|
||||||
def parse_lon_val_segment(segment) do
|
def parse_lon_val_segment(segment) do
|
||||||
case Regex.run(~r/lon=([\d.]+),lat=([\d.]+),val=([\d.eE+-]+)/, segment) do
|
case Regex.run(~r/lon=([\d.]+),lat=([\d.]+),val=([\d.eE+-]+)/, segment) do
|
||||||
[_, lon_str, lat_str, val_str] ->
|
[_, lon_str, lat_str, val_str] ->
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ defmodule Microwaveprop.Weather.GridCache do
|
||||||
def handle_call(:sync, _from, state), do: {:reply, :ok, state}
|
def handle_call(:sync, _from, state), do: {:reply, :ok, state}
|
||||||
|
|
||||||
def handle_call(:clear, _from, state) do
|
def handle_call(:clear, _from, state) do
|
||||||
if valkey_backend?(), do: valkey_clear(), else: :ets.delete_all_objects(@table)
|
_ = if valkey_backend?(), do: valkey_clear(), else: :ets.delete_all_objects(@table)
|
||||||
|
|
||||||
for {ref, _vt} <- state.monitors, do: Process.demonitor(ref, [:flush])
|
for {ref, _vt} <- state.monitors, do: Process.demonitor(ref, [:flush])
|
||||||
:ets.delete_all_objects(@lock_table)
|
:ets.delete_all_objects(@lock_table)
|
||||||
|
|
@ -291,10 +291,14 @@ defmodule Microwaveprop.Weather.GridCache do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ets_latest_valid_time do
|
defp ets_latest_valid_time do
|
||||||
:ets.foldl(fn
|
:ets.foldl(
|
||||||
|
fn
|
||||||
{vt, _}, nil -> vt
|
{vt, _}, nil -> vt
|
||||||
{vt, _}, latest -> if DateTime.compare(vt, latest) == :gt, do: vt, else: latest
|
{vt, _}, latest -> if DateTime.after?(vt, latest), do: vt, else: latest
|
||||||
end, nil, @table)
|
end,
|
||||||
|
nil,
|
||||||
|
@table
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ets_prune_keep_latest(keep) do
|
defp ets_prune_keep_latest(keep) do
|
||||||
|
|
@ -475,12 +479,13 @@ defmodule Microwaveprop.Weather.GridCache do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp valkey_clear do
|
defp valkey_clear do
|
||||||
|
_ =
|
||||||
case Valkey.scan_match("#{@valkey_chunk_prefix}*") do
|
case Valkey.scan_match("#{@valkey_chunk_prefix}*") do
|
||||||
{:ok, keys} -> Valkey.del(keys)
|
{:ok, keys} -> _ = Valkey.del(keys)
|
||||||
_ -> :ok
|
_ -> :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
Valkey.del([@valkey_index_key])
|
_ = Valkey.del([@valkey_index_key])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp chunk_key_pattern(valid_time) do
|
defp chunk_key_pattern(valid_time) do
|
||||||
|
|
|
||||||
|
|
@ -57,10 +57,14 @@ defmodule Microwaveprop.Weather.NexradCache do
|
||||||
# Iterate ETS with foldl so frame payloads (~66 MB each) never land on the
|
# Iterate ETS with foldl so frame payloads (~66 MB each) never land on the
|
||||||
# calling process's heap — :ets.tab2list() at capacity would allocate ~1.3 GB.
|
# calling process's heap — :ets.tab2list() at capacity would allocate ~1.3 GB.
|
||||||
defp oldest_key do
|
defp oldest_key do
|
||||||
:ets.foldl(fn
|
:ets.foldl(
|
||||||
|
fn
|
||||||
{ts, _, _}, nil -> ts
|
{ts, _, _}, nil -> ts
|
||||||
{ts, _, _}, oldest -> if DateTime.before?(ts, oldest), do: ts, else: oldest
|
{ts, _, _}, oldest -> if DateTime.before?(ts, oldest), do: ts, else: oldest
|
||||||
end, nil, @table)
|
end,
|
||||||
|
nil,
|
||||||
|
@table
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec prune_older_than(DateTime.t()) :: non_neg_integer()
|
@spec prune_older_than(DateTime.t()) :: non_neg_integer()
|
||||||
|
|
|
||||||
|
|
@ -134,9 +134,7 @@ defmodule Microwaveprop.Weather.ScalarFile do
|
||||||
# leave stale chunks behind.
|
# leave stale chunks behind.
|
||||||
case File.ls(dir) do
|
case File.ls(dir) do
|
||||||
{:ok, entries} ->
|
{:ok, entries} ->
|
||||||
for entry <- entries do
|
Enum.each(entries, &File.rm!(Path.join(dir, &1)))
|
||||||
_ = File.rm(Path.join(dir, entry))
|
|
||||||
end
|
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
:ok
|
:ok
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ defmodule Microwaveprop.Workers.RoverPathProfileWorker do
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
Logger.error("RoverPathProfileWorker crashed for path=#{path.id}: #{Exception.message(e)}")
|
Logger.error("RoverPathProfileWorker crashed for path=#{path.id}: #{Exception.message(e)}")
|
||||||
store_failed(path, "exception: #{Exception.message(e)}")
|
_ = store_failed(path, "exception: #{Exception.message(e)}")
|
||||||
reraise e, __STACKTRACE__
|
reraise e, __STACKTRACE__
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -128,7 +128,7 @@ defmodule Microwaveprop.Workers.RoverPathProfileWorker do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, updated} ->
|
{:ok, updated} ->
|
||||||
broadcast(updated)
|
_ = broadcast(updated)
|
||||||
:ok
|
:ok
|
||||||
|
|
||||||
{:error, cs} ->
|
{:error, cs} ->
|
||||||
|
|
@ -216,7 +216,7 @@ defmodule Microwaveprop.Workers.RoverPathProfileWorker do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, updated} ->
|
{:ok, updated} ->
|
||||||
broadcast(updated)
|
_ = broadcast(updated)
|
||||||
{:error, message}
|
{:error, message}
|
||||||
|
|
||||||
{:error, cs} ->
|
{:error, cs} ->
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ defmodule MicrowavepropWeb.Api.RateLimiter do
|
||||||
"""
|
"""
|
||||||
@spec init_table() :: :ok
|
@spec init_table() :: :ok
|
||||||
def init_table do
|
def init_table do
|
||||||
|
_ =
|
||||||
if :ets.whereis(@table) == :undefined do
|
if :ets.whereis(@table) == :undefined do
|
||||||
try do
|
try do
|
||||||
:ets.new(@table, [:set, :public, :named_table, write_concurrency: true])
|
:ets.new(@table, [:set, :public, :named_table, write_concurrency: true])
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,12 @@ defmodule MicrowavepropWeb.ScoresController do
|
||||||
|
|
||||||
{lats, lons, score_bytes} =
|
{lats, lons, score_bytes} =
|
||||||
Enum.reduce(scores, {[], [], []}, fn s, {lats_acc, lons_acc, scs_acc} ->
|
Enum.reduce(scores, {[], [], []}, fn s, {lats_acc, lons_acc, scs_acc} ->
|
||||||
{[<<s.lat * 1.0::little-float-32>> | lats_acc],
|
{[<<s.lat * 1.0::little-float-32>> | lats_acc], [<<s.lon * 1.0::little-float-32>> | lons_acc],
|
||||||
[<<s.lon * 1.0::little-float-32>> | lons_acc],
|
|
||||||
[<<clamp_score(s.score)::8>> | scs_acc]}
|
[<<clamp_score(s.score)::8>> | scs_acc]}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
<<header::binary, IO.iodata_to_binary(lats)::binary,
|
<<header::binary, IO.iodata_to_binary(lats)::binary, IO.iodata_to_binary(lons)::binary,
|
||||||
IO.iodata_to_binary(lons)::binary, IO.iodata_to_binary(score_bytes)::binary>>
|
IO.iodata_to_binary(score_bytes)::binary>>
|
||||||
end
|
end
|
||||||
|
|
||||||
defp clamp_score(s) when is_integer(s) and s < 0, do: 0
|
defp clamp_score(s) when is_integer(s) and s < 0, do: 0
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
|
|
||||||
def handle_event("delete_contact", _params, socket) do
|
def handle_event("delete_contact", _params, socket) do
|
||||||
if admin?(socket.assigns) do
|
if admin?(socket.assigns) do
|
||||||
Radio.delete_contact!(socket.assigns.contact)
|
_ = Radio.delete_contact!(socket.assigns.contact)
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
|> push_navigate(to: ~p"/rover-planning")}
|
|> push_navigate(to: ~p"/rover-planning")}
|
||||||
|
|
||||||
%Mission{} = mission ->
|
%Mission{} = mission ->
|
||||||
|
_ =
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "rover_planning:#{mission.id}")
|
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "rover_planning:#{mission.id}")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,6 @@ defmodule MicrowavepropWeb.SkewtLocationResolver do
|
||||||
|
|
||||||
{:error, reason} when is_binary(reason) ->
|
{:error, reason} when is_binary(reason) ->
|
||||||
{:error, "callsign lookup failed: #{reason}"}
|
{:error, "callsign lookup failed: #{reason}"}
|
||||||
|
|
||||||
{:error, _} ->
|
|
||||||
{:error, "callsign lookup failed"}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -83,9 +80,6 @@ defmodule MicrowavepropWeb.SkewtLocationResolver do
|
||||||
|
|
||||||
{:error, reason} when is_binary(reason) ->
|
{:error, reason} when is_binary(reason) ->
|
||||||
{:error, "geocode failed: #{reason}"}
|
{:error, "geocode failed: #{reason}"}
|
||||||
|
|
||||||
{:error, _} ->
|
|
||||||
{:error, "geocode failed"}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ defmodule MicrowavepropWeb.SkewtSvg do
|
||||||
ascent line is overlaid on the temperature trace, matching the
|
ascent line is overlaid on the temperature trace, matching the
|
||||||
surface-parcel rendition the SPC sounding viewer publishes.
|
surface-parcel rendition the SPC sounding viewer publishes.
|
||||||
"""
|
"""
|
||||||
@spec render([map()], keyword()) :: iodata()
|
@spec render([map()], keyword()) :: list()
|
||||||
def render(profile, opts \\ []) when is_list(profile) do
|
def render(profile, opts \\ []) when is_list(profile) do
|
||||||
cleaned = clean_profile(profile)
|
cleaned = clean_profile(profile)
|
||||||
parcel = Keyword.get(opts, :parcel_trace, [])
|
parcel = Keyword.get(opts, :parcel_trace, [])
|
||||||
|
|
|
||||||
|
|
@ -220,5 +220,4 @@ defmodule Mix.Tasks.Calibrate.Aprs144 do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp format_float(f) when is_float(f), do: :erlang.float_to_binary(f, decimals: 4)
|
defp format_float(f) when is_float(f), do: :erlang.float_to_binary(f, decimals: 4)
|
||||||
defp format_float(f), do: to_string(f)
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ defmodule Mix.Tasks.Unused do
|
||||||
|
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
|
@dialyzer {:no_opaque, run: 1}
|
||||||
|
|
||||||
# Callbacks that are invoked by the runtime, not by direct function calls.
|
# Callbacks that are invoked by the runtime, not by direct function calls.
|
||||||
# They look unused to static analysis but are actually called by OTP /
|
# They look unused to static analysis but are actually called by OTP /
|
||||||
# Phoenix / LiveView machinery.
|
# Phoenix / LiveView machinery.
|
||||||
|
|
@ -376,6 +378,7 @@ defmodule Mix.Tasks.Unused do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec analyze_beam(String.t()) :: {MapSet.t(), MapSet.t()}
|
||||||
defp analyze_beam(beam_file) do
|
defp analyze_beam(beam_file) do
|
||||||
module = beam_module(beam_file)
|
module = beam_module(beam_file)
|
||||||
|
|
||||||
|
|
@ -388,7 +391,7 @@ defmodule Mix.Tasks.Unused do
|
||||||
|
|
||||||
{defs, calls}
|
{defs, calls}
|
||||||
|
|
||||||
{:error, _reason} ->
|
_ ->
|
||||||
# Binary module or unreadable — skip
|
# Binary module or unreadable — skip
|
||||||
{MapSet.new(), MapSet.new()}
|
{MapSet.new(), MapSet.new()}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
1
mix.lock
1
mix.lock
|
|
@ -82,7 +82,6 @@
|
||||||
"text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
|
"text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
|
||||||
"thousand_island": {:hex, :thousand_island, "1.5.0", "f50a213cac97262b6d5ebb85745aa2c00fec1413191e6e66834788d45425cecb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "708923d40523e43cf99041ab37a0d4b0ec426ac6438fa3716ab23d919eaeb412"},
|
"thousand_island": {:hex, :thousand_island, "1.5.0", "f50a213cac97262b6d5ebb85745aa2c00fec1413191e6e66834788d45425cecb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "708923d40523e43cf99041ab37a0d4b0ec426ac6438fa3716ab23d919eaeb412"},
|
||||||
"tidewave": {:hex, :tidewave, "0.5.6", "91f35540b5599640443f1d3a1c6166bf506e202840261a6344e384e8813c1f64", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "dc82d52b8b6ffc04680544b17cd340c7d4166bb0d63999eb960850526866b533"},
|
"tidewave": {:hex, :tidewave, "0.5.6", "91f35540b5599640443f1d3a1c6166bf506e202840261a6344e384e8813c1f64", [:mix], [{:circular_buffer, "~> 0.4 or ~> 1.0", [hex: :circular_buffer, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_reload, ">= 1.6.1", [hex: :phoenix_live_reload, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "dc82d52b8b6ffc04680544b17cd340c7d4166bb0d63999eb960850526866b533"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
|
|
||||||
"uniq": {:hex, :uniq, "0.6.3", "68acff834cce1817b52928ef346662735c5413a4fec9c3b0d4a9126de5b2b489", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "2b2a900d0a20f3a55d3de0bc8150495e4a71255734dfb23889991bda5aca6c7d"},
|
"uniq": {:hex, :uniq, "0.6.3", "68acff834cce1817b52928ef346662735c5413a4fec9c3b0d4a9126de5b2b489", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "2b2a900d0a20f3a55d3de0bc8150495e4a71255734dfb23889991bda5aca6c7d"},
|
||||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||||
"websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},
|
"websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue