Enabled :error_handling, :unknown, :unmatched_returns, :extra_return, :missing_return in an earlier commit and landed a 129-warning baseline. Four parallel agents each fixed a directory slice: - Core contexts (29): Radio, Release, Weather, Beacons, Cache, Backtest.Features, Terrain.Srtm, Ionosphere.GiroClient, Propagation.RunTiming, Accounts.Scope, RepoListener. Fixes were (a) prefix side-effect calls (Task.start, Phoenix.PubSub, Logger, :ets.new) with _ = ; (b) tighten/widen specs that didn't match actual returns; (c) add missing @type t declarations; (d) drop dead parse_int(nil) clause. - Propagation + weather subdirs (15): FreshnessMonitor, NotifyListener, ScoreCache, ScoreCacheReconciler, Weather.FrontalAnalysis, Weather.Grib2.Extractor, Weather.Grib2.Wgrib2, GridCache, HrrrPointEnqueuer, NexradCache. Same patterns — mostly _ = on PubSub / :ets / Repo.insert_all; widened two specs (float -> number) where integer returns were reachable. - Workers (35): BackfillEnqueue, CanadianSoundingFetch, ContactImport, ContactWeatherEnqueue, GefsFetch, IemreFetch, NarrFetch, SolarIndex, TerrainProfile, WeatherFetch. Prefixed Repo.update_all / Radio.set_enrichment_status! / Weather.upsert_* side-effect calls. Fixed one :pattern_match in CanadianSoundingFetch.most_recent_sounding_time/1 where a tautological cond guard generated unreachable code. - Web + Mix tasks + lib_ml (46 of 50): controllers, LiveViews, UserAuth, and 11 mix tasks. Same prefix strategy. 4 remaining warnings originate in LiveTable.LiveResource dep macro expansion and can't be fixed without forking the dep — added .dialyzer_ignore.exs to suppress just those specific file:line pairs. Also wired ignore_warnings in mix.exs dialyzer config. mix dialyzer --format short | grep ^lib/ | wc -l -> 0 mix test: 2163 tests, 3 pre-existing flakes, 0 regressions.
102 lines
3.1 KiB
Elixir
102 lines
3.1 KiB
Elixir
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
|