feat(accounts): derive 10-char home_grid from explicit lat/lon

This commit is contained in:
Graham McIntire 2026-04-25 17:04:45 -05:00
parent 9796f06ba2
commit 9bfd345e8d
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 50 additions and 3 deletions

View file

@ -220,9 +220,10 @@ defmodule Microwaveprop.Accounts.User do
Accepts either a Maidenhead grid or an explicit lat/lon. When only the
grid is given, lat/lon are derived from its centre; when only lat/lon
are given, the grid is left untouched. An empty `home_grid` paired
with explicit nil lat/lon clears all home fields (used by the "clear
home" affordance on /rover).
are given, a 10-character grid is computed from the coordinate pair so
callers always end up with both fields populated. An empty `home_grid`
paired with explicit nil lat/lon clears all home fields (used by the
"clear home" affordance on /rover).
"""
@spec change_home_qth(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
def change_home_qth(user, attrs) do
@ -231,6 +232,7 @@ defmodule Microwaveprop.Accounts.User do
|> normalize_home_grid()
|> validate_home_grid_format()
|> derive_home_latlon_from_grid()
|> derive_home_grid_from_latlon()
|> validate_home_latlon_range()
|> require_home_qth_pair()
end
@ -294,6 +296,30 @@ defmodule Microwaveprop.Accounts.User do
end
end
# If lat/lon were provided without a grid, encode a 10-char Maidenhead
# grid from the coordinates so the UI always has a human-readable label
# to render and downstream callers can rely on `home_grid` being set
# whenever the QTH is set. Skipped on invalid changesets (lat/lon range
# check has not run yet) and when both fields aren't numeric.
defp derive_home_grid_from_latlon(changeset) do
lat = get_field(changeset, :home_lat)
lon = get_field(changeset, :home_lon)
cond do
not changeset.valid? ->
changeset
not (is_number(lat) and is_number(lon)) ->
changeset
get_field(changeset, :home_grid) not in [nil, ""] ->
changeset
true ->
put_change(changeset, :home_grid, Maidenhead.from_latlon(lat * 1.0, lon * 1.0, 10))
end
end
defp validate_home_latlon_range(changeset) do
changeset
|> validate_number(:home_lat, greater_than_or_equal_to: -90.0, less_than_or_equal_to: 90.0)

View file

@ -461,6 +461,27 @@ defmodule Microwaveprop.Accounts.UserTest do
assert Ecto.Changeset.get_change(changeset, :home_lon) == -96.7
end
test "derives a 10-char home_grid when lat/lon are given without one" do
changeset = User.change_home_qth(%User{}, %{"home_lat" => 33.10, "home_lon" => -96.625})
assert changeset.valid?
grid = Ecto.Changeset.get_change(changeset, :home_grid)
assert is_binary(grid)
assert String.length(grid) == 10
assert String.starts_with?(grid, "EM13qc")
end
test "preserves an explicit grid when both grid and lat/lon are provided" do
changeset =
User.change_home_qth(%User{}, %{
"home_grid" => "EM13qc",
"home_lat" => 33.10,
"home_lon" => -96.625
})
assert changeset.valid?
assert Ecto.Changeset.get_change(changeset, :home_grid) == "EM13qc"
end
test "rejects invalid grids" do
changeset = User.change_home_qth(%User{}, %{"home_grid" => "ZZ99zz"})
refute changeset.valid?