fix remaining bugs and quality improvements
Bug fixes: - path_compute: use midpoint longitude (src+dest)/2 for time-of-day score - rate_limiter: guard nil token id from burning auth rate limit bucket Robustness: - scorer: replace fragile MatchError-prone pattern match with per-key fallback — each invariant computed individually if absent - telemetry: log exception in rescue instead of silent swallow - grid: add atom-key clause to contains?() consistent with point_source/1 - grid: round float_range values to avoid fp drift in snapped lookups Cleanup: - path_compute: remove unused alias Geo (import-only module)
This commit is contained in:
parent
e3d430f8c4
commit
4d3b61c740
7 changed files with 31 additions and 20 deletions
|
|
@ -60,6 +60,10 @@ defmodule Microwaveprop.Propagation.Grid do
|
||||||
lat >= @lat_min and lat <= @lat_max and lon >= @lon_min and lon <= @lon_max
|
lat >= @lat_min and lat <= @lat_max and lon >= @lon_min and lon <= @lon_max
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contains?(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon) do
|
||||||
|
lat >= @lat_min and lat <= @lat_max and lon >= @lon_min and lon <= @lon_max
|
||||||
|
end
|
||||||
|
|
||||||
def contains?(_), do: false
|
def contains?(_), do: false
|
||||||
|
|
||||||
@doc "Returns the grid specification for wgrib2 -lola extraction."
|
@doc "Returns the grid specification for wgrib2 -lola extraction."
|
||||||
|
|
@ -133,6 +137,9 @@ defmodule Microwaveprop.Propagation.Grid do
|
||||||
|
|
||||||
defp float_range(start, stop, step) do
|
defp float_range(start, stop, step) do
|
||||||
count = round((stop - start) / step) + 1
|
count = round((stop - start) / step) + 1
|
||||||
Enum.map(0..(count - 1), fn i -> start + i * step end)
|
# Avoid floating-point drift by rounding each value to 3 decimals.
|
||||||
|
# The accumulation of start + i * step drifts beyond 0.001° after ~200
|
||||||
|
# iterations, which can cause map lookups on snapped coordinates to miss.
|
||||||
|
Enum.map(0..(count - 1), fn i -> Float.round(start + i * step, 3) end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ defmodule Microwaveprop.Propagation.PathCompute do
|
||||||
|
|
||||||
import Microwaveprop.Geo, only: [haversine_km: 4, bearing_deg: 4]
|
import Microwaveprop.Geo, only: [haversine_km: 4, bearing_deg: 4]
|
||||||
|
|
||||||
alias Microwaveprop.Geo
|
|
||||||
alias Microwaveprop.Ionosphere
|
alias Microwaveprop.Ionosphere
|
||||||
alias Microwaveprop.Propagation
|
alias Microwaveprop.Propagation
|
||||||
alias Microwaveprop.Propagation.BandConfig
|
alias Microwaveprop.Propagation.BandConfig
|
||||||
|
|
@ -405,7 +404,7 @@ defmodule Microwaveprop.Propagation.PathCompute do
|
||||||
utc_minute: now.minute,
|
utc_minute: now.minute,
|
||||||
month: now.month,
|
month: now.month,
|
||||||
latitude: (src.lat + dst.lat) / 2,
|
latitude: (src.lat + dst.lat) / 2,
|
||||||
longitude: src.lon,
|
longitude: (src.lon + dst.lon) / 2,
|
||||||
pressure_mb: if(pressures != [], do: Enum.min(pressures)),
|
pressure_mb: if(pressures != [], do: Enum.min(pressures)),
|
||||||
prev_pressure_mb: nil,
|
prev_pressure_mb: nil,
|
||||||
rain_rate_mmhr: 0.0,
|
rain_rate_mmhr: 0.0,
|
||||||
|
|
@ -485,6 +484,5 @@ defmodule Microwaveprop.Propagation.PathCompute do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Avoid unused-alias warnings for the import.
|
|
||||||
@compile {:no_warn_undefined, Geo}
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -553,17 +553,10 @@ defmodule Microwaveprop.Propagation.Scorer do
|
||||||
# scorer for per-point reuse across bands) or all absent. We check
|
# scorer for per-point reuse across bands) or all absent. We check
|
||||||
# one key and derive the rest from the same branch so we never
|
# one key and derive the rest from the same branch so we never
|
||||||
# silently mix cached and freshly-computed values.
|
# silently mix cached and freshly-computed values.
|
||||||
%{
|
tod_score = conditions[:tod_score] || elem(score_time_of_day(conditions.utc_hour, conditions.utc_minute, conditions.month, conditions.longitude), 0)
|
||||||
tod_score: tod_score,
|
sky_score = conditions[:sky_score] || score_sky(conditions.sky_cover_pct)
|
||||||
sky_score: sky_score,
|
wind_score = conditions[:wind_score] || score_wind(conditions.wind_speed_kts)
|
||||||
wind_score: wind_score,
|
pressure_score = conditions[:pressure_score] || score_pressure(conditions.pressure_mb, conditions.prev_pressure_mb)
|
||||||
pressure_score: pressure_score
|
|
||||||
} =
|
|
||||||
if Map.has_key?(conditions, :tod_score) do
|
|
||||||
conditions
|
|
||||||
else
|
|
||||||
precompute_band_invariants(conditions)
|
|
||||||
end
|
|
||||||
|
|
||||||
factors = %{
|
factors = %{
|
||||||
humidity: score_humidity(conditions.abs_humidity, band_config),
|
humidity: score_humidity(conditions.abs_humidity, band_config),
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ defmodule Microwaveprop.Radio.Contact do
|
||||||
|> validate_callsign(:station2)
|
|> validate_callsign(:station2)
|
||||||
|> validate_grid_format(:grid1)
|
|> validate_grid_format(:grid1)
|
||||||
|> validate_grid_format(:grid2)
|
|> validate_grid_format(:grid2)
|
||||||
|> validate_inclusion(:mode, @allowed_modes)
|
|> validate_mode_inclusion()
|
||||||
|> validate_inclusion(:band, @allowed_bands)
|
|> validate_inclusion(:band, @allowed_bands)
|
||||||
|> validate_email_format()
|
|> validate_email_format()
|
||||||
|> validate_length(:submitter_email, max: 254)
|
|> validate_length(:submitter_email, max: 254)
|
||||||
|
|
@ -168,6 +168,16 @@ defmodule Microwaveprop.Radio.Contact do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Only run validate_inclusion for mode when the field has a non-nil change.
|
||||||
|
# nil means "not provided" (mode is optional), and put_change to nil would
|
||||||
|
# otherwise cause validate_inclusion to reject nil as not in the allowed list.
|
||||||
|
defp validate_mode_inclusion(changeset) do
|
||||||
|
case get_change(changeset, :mode) do
|
||||||
|
nil -> changeset
|
||||||
|
mode -> validate_inclusion(changeset, :mode, @allowed_modes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Either a user_id (logged-in submitter) or a submitter_email (anonymous)
|
# Either a user_id (logged-in submitter) or a submitter_email (anonymous)
|
||||||
# must be present so we know who submitted the contact.
|
# must be present so we know who submitted the contact.
|
||||||
defp validate_user_or_email(changeset) do
|
defp validate_user_or_email(changeset) do
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ defmodule MicrowavepropWeb.Api.RateLimiter do
|
||||||
|
|
||||||
defp bucket_for(conn, opts) do
|
defp bucket_for(conn, opts) do
|
||||||
case conn.assigns[:current_api_token] do
|
case conn.assigns[:current_api_token] do
|
||||||
%{id: id} ->
|
%{id: id} when is_binary(id) ->
|
||||||
{{:token, id}, Keyword.get(opts, :auth_limit, @default_auth_limit)}
|
{{:token, id}, Keyword.get(opts, :auth_limit, @default_auth_limit)}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,9 @@ defmodule MicrowavepropWeb.Telemetry do
|
||||||
:ok
|
:ok
|
||||||
rescue
|
rescue
|
||||||
# Don't crash the poller if the DB blips — telemetry is best-effort.
|
# Don't crash the poller if the DB blips — telemetry is best-effort.
|
||||||
_ -> :ok
|
e ->
|
||||||
|
require Logger
|
||||||
|
Logger.warning("Oban queue depth poll failed: #{inspect(e)}")
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ defmodule MicrowavepropWeb.UserAuth do
|
||||||
|
|
||||||
# Do not renew session if the user is already logged in
|
# Do not renew session if the user is already logged in
|
||||||
# to prevent CSRF errors or data being lost in tabs that are still open
|
# to prevent CSRF errors or data being lost in tabs that are still open
|
||||||
defp renew_session(conn, user) when conn.assigns.current_scope.user.id == user.id do
|
defp renew_session(conn, %{id: user_id}) when conn.assigns.current_scope.user.id == user_id do
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue