defmodule Microwaveprop.Workers.TerrainProfileWorker do @moduledoc false use Oban.Worker, queue: :terrain, max_attempts: 20, unique: [period: 300, states: [:available, :scheduled, :executing, :retryable]] 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: %{"contact_id" => contact_id}}) do Microwaveprop.Instrument.span([:worker, :terrain_profile], %{contact_id: contact_id}, fn -> if Terrain.has_terrain_profile?(contact_id) do _ = Radio.set_enrichment_status!([contact_id], :terrain_status, :complete) :ok else analyse_terrain(contact_id) end end) end defp analyse_terrain(contact_id) do contact = Radio.get_contact!(contact_id) with %{"lat" => lat1} <- contact.pos1, lon1 when is_number(lon1) <- contact.pos1["lon"], %{"lat" => lat2} <- contact.pos2, lon2 when is_number(lon2) <- contact.pos2["lon"] do fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2) else _ -> _ = Radio.set_enrichment_status!([contact_id], :terrain_status, :unavailable) :ok end end defp fetch_and_store_profile(contact_id, contact, lat1, lon1, lat2, lon2) do dist_km = Decimal.to_float(contact.distance_km || Decimal.new(0)) freq_ghz = Decimal.to_float(contact.band) / 1000 k = lookup_k_factor(contact) case ElevationClient.fetch_elevation_profile(lat1, lon1, lat2, lon2, 64, download: true) do {:ok, profile} -> store_terrain_result(contact_id, profile, dist_km, freq_ghz, k) {:error, reason} -> {:error, reason} end end defp store_terrain_result(contact_id, profile, dist_km, freq_ghz, k) do 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(%{ contact_id: contact_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 }) _ = Radio.set_enrichment_status!([contact_id], :terrain_status, :complete) _ = Phoenix.PubSub.broadcast( Microwaveprop.PubSub, "contact_enrichment:#{contact_id}", {:terrain_ready, contact_id} ) :ok 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