Fetch elevation data along the path between two stations via the Open-Meteo Elevation API (with Open-Topo-Data fallback), compute Fresnel zone clearance, earth bulge, and knife-edge diffraction loss, and store the results per QSO. - terrain_profiles table with migration and TerrainProfile schema - ElevationClient with batched API calls and fallback - TerrainAnalysis with Fresnel/diffraction physics (ITU-R P.526-15) - TerrainProfileWorker on Oban :terrain queue - QsoWeatherEnqueueWorker enqueues terrain jobs automatically - QSO show page displays verdict badge and collapsible elevation table - Reorder show page: terrain, soundings, solar, HRRR, surface obs - Fix Dockerfile wgrib2 build (add cmake dependency)
94 lines
2.2 KiB
Elixir
94 lines
2.2 KiB
Elixir
defmodule Microwaveprop.Terrain.TerrainProfileTest do
|
|
use Microwaveprop.DataCase, async: true
|
|
|
|
alias Microwaveprop.Radio.Qso
|
|
alias Microwaveprop.Terrain.TerrainProfile
|
|
|
|
@qso_attrs %{
|
|
station1: "W5XD",
|
|
station2: "K5TR",
|
|
qso_timestamp: ~U[2026-03-28 18:00:00Z],
|
|
mode: "CW",
|
|
band: Decimal.new("1296"),
|
|
pos1: %{"lat" => 32.9, "lon" => -97.0},
|
|
pos2: %{"lat" => 30.3, "lon" => -97.7}
|
|
}
|
|
|
|
defp create_qso do
|
|
{:ok, qso} =
|
|
%Qso{}
|
|
|> Qso.changeset(@qso_attrs)
|
|
|> Repo.insert()
|
|
|
|
qso
|
|
end
|
|
|
|
describe "changeset/2" do
|
|
test "valid with required fields" do
|
|
qso = create_qso()
|
|
|
|
attrs = %{
|
|
qso_id: qso.id,
|
|
sample_count: 65,
|
|
path_points: [%{"lat" => 32.9, "lon" => -97.0, "elev" => 200.0, "dist_km" => 0.0}],
|
|
verdict: "CLEAR"
|
|
}
|
|
|
|
changeset = TerrainProfile.changeset(%TerrainProfile{}, attrs)
|
|
assert changeset.valid?
|
|
end
|
|
|
|
test "invalid without required fields" do
|
|
changeset = TerrainProfile.changeset(%TerrainProfile{}, %{})
|
|
refute changeset.valid?
|
|
|
|
errors = errors_on(changeset)
|
|
assert errors[:qso_id]
|
|
assert errors[:sample_count]
|
|
assert errors[:path_points]
|
|
assert errors[:verdict]
|
|
end
|
|
|
|
test "valid with all optional fields" do
|
|
qso = create_qso()
|
|
|
|
attrs = %{
|
|
qso_id: qso.id,
|
|
sample_count: 65,
|
|
path_points: [%{"lat" => 32.9, "lon" => -97.0, "elev" => 200.0, "dist_km" => 0.0}],
|
|
verdict: "BLOCKED",
|
|
max_elevation_m: 450.0,
|
|
min_clearance_m: -12.5,
|
|
diffraction_db: 8.3,
|
|
fresnel_hit_count: 3,
|
|
obstructed_count: 1
|
|
}
|
|
|
|
changeset = TerrainProfile.changeset(%TerrainProfile{}, attrs)
|
|
assert changeset.valid?
|
|
end
|
|
|
|
test "enforces unique qso_id constraint on insert" do
|
|
qso = create_qso()
|
|
|
|
attrs = %{
|
|
qso_id: qso.id,
|
|
sample_count: 65,
|
|
path_points: [],
|
|
verdict: "CLEAR"
|
|
}
|
|
|
|
{:ok, _} =
|
|
%TerrainProfile{}
|
|
|> TerrainProfile.changeset(attrs)
|
|
|> Repo.insert()
|
|
|
|
{:error, changeset} =
|
|
%TerrainProfile{}
|
|
|> TerrainProfile.changeset(attrs)
|
|
|> Repo.insert()
|
|
|
|
assert errors_on(changeset)[:qso_id]
|
|
end
|
|
end
|
|
end
|