defmodule Microwaveprop.Workers.TerrainProfileWorker do @moduledoc false use Oban.Worker, queue: :terrain, max_attempts: 20 alias Microwaveprop.Radio alias Microwaveprop.Terrain alias Microwaveprop.Terrain.ElevationClient alias Microwaveprop.Terrain.TerrainAnalysis alias Microwaveprop.Weather @impl Oban.Worker def backoff(%Oban.Job{attempt: attempt}) do min(120 * Integer.pow(2, attempt - 1), _six_hours = 21_600) end @impl Oban.Worker def perform(%Oban.Job{args: %{"qso_id" => qso_id}}) do if Terrain.has_terrain_profile?(qso_id) do :ok else qso = Radio.get_qso!(qso_id) lat1 = qso.pos1["lat"] lon1 = qso.pos1["lon"] || qso.pos1["lng"] lat2 = qso.pos2["lat"] lon2 = qso.pos2["lon"] || qso.pos2["lng"] dist_km = Decimal.to_float(qso.distance_km) freq_ghz = Decimal.to_float(qso.band) / 1000 # Look up HRRR refractivity gradient for dynamic k-factor k = lookup_k_factor(qso) case ElevationClient.fetch_elevation_profile(lat1, lon1, lat2, lon2) do {:ok, profile} -> analysis = TerrainAnalysis.analyse(profile, dist_km, freq_ghz, k_factor: k) path_points = Enum.map(profile, fn p -> %{ "lat" => p.lat, "lon" => p.lon, "d" => p.d, "elev" => p.elev, "dist_km" => p.dist_km } end) Terrain.upsert_terrain_profile(%{ qso_id: qso_id, sample_count: length(profile), path_points: path_points, max_elevation_m: analysis.max_elevation_m, min_clearance_m: analysis.min_clearance_m, diffraction_db: analysis.diffraction_db, fresnel_hit_count: analysis.fresnel_hit_count, obstructed_count: analysis.obstructed_count, verdict: analysis.verdict }) :ok {:error, reason} -> {:error, reason} end end end defp lookup_k_factor(qso) do case Weather.hrrr_for_qso(qso) do %{min_refractivity_gradient: grad} when not is_nil(grad) -> TerrainAnalysis.k_factor(grad) _ -> 4 / 3 end end end