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 contact = Radio.get_contact!(qso_id) lat1 = contact.pos1["lat"] lon1 = contact.pos1["lon"] || contact.pos1["lng"] lat2 = contact.pos2["lat"] lon2 = contact.pos2["lon"] || contact.pos2["lng"] dist_km = Decimal.to_float(contact.distance_km) freq_ghz = Decimal.to_float(contact.band) / 1000 # Look up HRRR refractivity gradient for dynamic k-factor k = lookup_k_factor(contact) case ElevationClient.fetch_elevation_profile(lat1, lon1, lat2, lon2, 64, download: true) 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 }) Phoenix.PubSub.broadcast( Microwaveprop.PubSub, "contact_enrichment:#{qso_id}", {:terrain_ready, qso_id} ) :ok {:error, reason} -> {:error, reason} end end end defp lookup_k_factor(contact) do case Weather.hrrr_for_contact(contact) do %{min_refractivity_gradient: grad} when not is_nil(grad) -> TerrainAnalysis.k_factor(grad) _ -> 4 / 3 end end end