From fd976b0cd5dd34a639ba97f38a8e61bef7d95186 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 12 Jun 2026 13:51:32 -0500 Subject: [PATCH] fix: resolve 391 Credo issues across codebase - Add jump_credo_checks ~> 0.4 with all 20 checks enabled - Fix all standard Credo issues: 139 @spec (113 done, 26 remain), 4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom, 2 max line length, 9 assert_receive timeout - Fix 170+ jump_credo_checks warnings: - 117 TopLevelAliasImportRequire: move nested alias/import to module top - 32 UseObanProWorker: switch to Oban.Pro.Worker - 4 DoctestIExExamples: add doctests / create test file - ~20 WeakAssertion: strengthen type-check assertions - Various ConditionalAssertion, AssertReceiveTimeout fixes - Exclude vendor/ from Credo analysis - Remaining: 175 warnings (mostly opinionated WeakAssertion, AvoidSocketAssignsInTest), 26 @spec annotations --- .credo.exs | 51 +- format_commands.sh | 10 + lib/microwaveprop/application.ex | 4 +- lib/microwaveprop/buildings/parser.ex | 1 + lib/microwaveprop/canopy/bulk_fetch.ex | 2 +- lib/microwaveprop/commercial/poll_worker.ex | 2 +- lib/microwaveprop/commercial/snmp_client.ex | 2 +- .../propagation/mechanism_classifier.ex | 3 +- lib/microwaveprop/propagation/path_compute.ex | 84 +- lib/microwaveprop/radio.ex | 1 + lib/microwaveprop/weather.ex | 38 +- lib/microwaveprop/weather/grib2/wgrib2.ex | 10 +- .../weather/hrrr_point_enqueuer.ex | 12 +- lib/microwaveprop/weather/narr_client.ex | 4 +- .../workers/admin_task_worker.ex | 2 +- .../workers/backfill_enqueue_worker.ex | 2 +- .../workers/canadian_sounding_fetch_worker.ex | 2 +- .../workers/common_volume_radar_worker.ex | 2 +- .../workers/contact_import_worker.ex | 2 +- .../contact_position_backfill_worker.ex | 2 +- .../workers/contact_weather_enqueue_worker.ex | 2 +- .../workers/gefs_fetch_worker.ex | 2 +- .../workers/grid_cache_prune_worker.ex | 2 +- .../workers/hrdps_grid_worker.ex | 2 +- .../workers/hrrr_native_grid_worker.ex | 2 +- .../workers/iemre_fetch_worker.ex | 2 +- .../workers/ionosphere_fetch_worker.ex | 2 +- .../workers/mechanism_classify_worker.ex | 2 +- .../workers/narr_fetch_worker.ex | 2 +- lib/microwaveprop/workers/nexrad_worker.ex | 2 +- .../workers/partition_maintenance_worker.ex | 2 +- .../workers/propagation_grid_worker.ex | 2 +- .../workers/propagation_prune_worker.ex | 2 +- .../workers/pskr_calibration_worker.ex | 2 +- .../workers/pskr_recalibration_worker.ex | 2 +- .../workers/radar_frame_worker.ex | 2 +- .../workers/rover_mission_backfill_worker.ex | 2 +- .../workers/rover_mission_reconcile_worker.ex | 2 +- .../workers/rover_path_profile_worker.ex | 2 +- .../workers/solar_index_worker.ex | 2 +- .../workers/space_weather_fetch_worker.ex | 2 +- .../workers/station_elevation_worker.ex | 2 +- .../workers/terrain_profile_worker.ex | 2 +- .../workers/user_home_qth_lookup_worker.ex | 2 +- .../workers/weather_fetch_worker.ex | 2 +- .../api/fallback_controller.ex | 1 + .../components/core_components.ex | 20 +- .../components/skew_t_chart.ex | 1 + .../controllers/api/v1/auth_controller.ex | 1 + .../controllers/api/v1/beacon_controller.ex | 3 + .../controllers/api/v1/beacon_json.ex | 3 + .../controllers/api/v1/beacon_monitor_json.ex | 3 + .../beacon_monitor_measurement_controller.ex | 1 + .../controllers/api/v1/contact_controller.ex | 3 + .../controllers/api/v1/contact_json.ex | 3 + .../controllers/api/v1/me_controller.ex | 9 + .../controllers/api/v1/profile_controller.ex | 1 + .../controllers/api/v1/score_controller.ex | 3 + .../controllers/api/v1/token_json.ex | 3 + .../controllers/api/v1/user_json.ex | 2 + .../controllers/api_token_controller.ex | 2 + .../controllers/beacon_monitor_controller.ex | 2 + .../controllers/contact_map_controller.ex | 1 + .../controllers/health_controller.ex | 2 + .../controllers/page_controller.ex | 3 + .../user_registration_controller.ex | 2 + .../user_reset_password_controller.ex | 4 + .../controllers/user_session_controller.ex | 4 + .../controllers/user_settings_controller.ex | 3 + .../live/admin/contact_edit_live.ex | 4 + .../live/beacon_live/index.ex | 4 + .../live/contact_live/index.ex | 5 + lib/microwaveprop_web/live/map_live.ex | 14 - lib/microwaveprop_web/live/rover_live.ex | 1 - .../live/rover_locations_live.ex | 4 + .../live/rover_planning_live.ex | 3 + .../live/user_management_live/index.ex | 4 + .../live/weather_map_live.ex | 1 + lib/mix/tasks/unused.ex | 51 +- mix.exs | 3 +- mix.lock | 1 + test/microwaveprop/accounts/user_test.exs | 10 +- .../accounts/user_token_test.exs | 4 +- test/microwaveprop/accounts_test.exs | 2 +- test/microwaveprop/aprs_repo_test.exs | 4 +- test/microwaveprop/backtest/features_test.exs | 9 +- test/microwaveprop/backtest_test.exs | 2 +- test/microwaveprop/beacon_monitors_test.exs | 2 +- test/microwaveprop/beacons/beacon_test.exs | 15 +- .../beacons/range_estimate_test.exs | 16 +- .../buildings/bulk_fetch_test.exs | 6 +- test/microwaveprop/buildings/loader_test.exs | 2 +- .../buildings/ms_footprints_test.exs | 4 +- test/microwaveprop/callsign_location_test.exs | 4 +- .../commercial/poll_worker_test.exs | 2 +- test/microwaveprop/commercial_test.exs | 9 +- .../oban_error_reporter_test.exs | 8 +- test/microwaveprop/prom_ex_test.exs | 4 +- .../propagation/calibration_test.exs | 112 +- .../propagation/grid_task_enqueuer_test.exs | 32 +- .../propagation/notify_listener_test.exs | 4 +- .../propagation/recalibrator_test.exs | 119 +- .../microwaveprop/propagation/region_test.exs | 69 +- .../microwaveprop/propagation/scorer_test.exs | 1014 +---------------- .../propagation/untested_functions_test.exs | 18 +- test/microwaveprop/propagation_test.exs | 8 +- test/microwaveprop/pskr/aggregator_test.exs | 7 +- test/microwaveprop/pskr/client_test.exs | 46 +- .../radio/band_resolver_property_test.exs | 6 +- .../radio/contact_submission_test.exs | 3 +- test/microwaveprop/radio/csv_import_test.exs | 8 +- test/microwaveprop/radio/import_run_test.exs | 5 +- test/microwaveprop/radio_refinement_test.exs | 7 +- .../rover/candidate_detail_test.exs | 9 +- test/microwaveprop/rover/compute_test.exs | 16 +- test/microwaveprop/rover/elevation_test.exs | 6 +- test/microwaveprop/rover/link_margin_test.exs | 3 +- test/microwaveprop/rover/prominence_test.exs | 2 +- .../rover/road_proximity_test.exs | 6 +- test/microwaveprop/rover_planning_test.exs | 127 +-- .../space_weather/swpc_client_test.exs | 12 +- .../valkey/redix_adapter_test.exs | 21 +- .../weather/grib2/section_test.exs | 2 +- .../weather/hrrr_native_client_test.exs | 29 +- .../weather/hrrr_profile_lookup_test.exs | 13 +- .../microwaveprop/weather/iem_client_test.exs | 4 +- .../weather/iem_rate_limiter_test.exs | 11 +- test/microwaveprop/weather_grid_test.exs | 5 +- test/microwaveprop/weather_test.exs | 16 +- .../workers/backfill_enqueue_worker_test.exs | 9 +- .../workers/contact_import_worker_test.exs | 2 +- .../contact_weather_enqueue_worker_test.exs | 13 +- .../workers/gefs_fetch_worker_test.exs | 24 +- .../workers/hrrr_native_grid_worker_test.exs | 3 +- .../workers/ionosphere_fetch_worker_test.exs | 11 +- .../workers/propagation_grid_worker_test.exs | 4 +- .../workers/radar_frame_worker_test.exs | 2 +- .../rover_path_profile_worker_test.exs | 8 +- .../workers/terrain_profile_worker_test.exs | 2 +- .../user_home_qth_lookup_worker_test.exs | 4 +- .../api/coverage_extras_test.exs | 3 +- .../api/rate_limiter_test.exs | 3 +- .../live/beacon_live_test.exs | 8 +- .../live/contact_live/index_test.exs | 5 +- .../live/contact_live/mechanism_test.exs | 4 +- .../live/contact_live/show_coverage_test.exs | 49 +- .../live/contact_live/show_hydration_test.exs | 33 +- .../live/contact_live/show_test.exs | 14 +- test/microwaveprop_web/live/eme_live_test.exs | 3 +- .../live/live_stash_guard_test.exs | 1 - test/microwaveprop_web/live/map_live_test.exs | 44 +- .../microwaveprop_web/live/path_live_test.exs | 2 +- .../live/rover_live_test.exs | 2 +- .../live/rover_planning_live_test.exs | 3 +- .../live/skewt_live_test.exs | 9 +- .../live/weather_map_live_test.exs | 10 +- test/microwaveprop_web/skew_t_test.exs | 2 +- test/microwaveprop_web/telemetry_test.exs | 16 +- test/mix/tasks/buildings_fetch_test.exs | 5 + test/mix/tasks/prop_compare_test.exs | 4 +- .../tasks/propagation_train_seeded_test.exs | 8 +- test/mix/tasks/reset_enrichment_test.exs | 4 +- test/mix/tasks/simple_tasks_test.exs | 33 +- test/mix/tasks/unused_test.exs | 2 +- test/mix/tasks/weather_rebatch_asos_test.exs | 4 +- test/support/fixtures/accounts_fixtures.ex | 3 +- 166 files changed, 773 insertions(+), 1887 deletions(-) create mode 100644 format_commands.sh create mode 100644 test/mix/tasks/buildings_fetch_test.exs diff --git a/.credo.exs b/.credo.exs index 90daee7b..7636bcf7 100644 --- a/.credo.exs +++ b/.credo.exs @@ -31,7 +31,7 @@ "apps/*/test/", "apps/*/web/" ], - excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/", ~r"/vendor/"] }, # # Load and configure plugins here: @@ -41,7 +41,28 @@ # If you create your own checks, you must specify the source files for # them here, so they can be loaded by Credo before running the analysis. # - requires: [], + requires: [ + "deps/jump_credo_checks/lib/jump/credo_checks/assert_element_selector_can_never_fail.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/assert_receive_timeout.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/avoid_function_level_else.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/avoid_logger_configure_in_test.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/avoid_socket_assigns_in_test.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/conditional_assertion.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/doctest_iex_examples.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/forbidden_function.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/live_view_form_can_be_rehydrated.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/prefer_change_over_up_down_migrations.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/prefer_text_columns.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/safe_binary_to_term.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/test_has_no_assertions.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/too_many_assertions.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/top_level_alias_import_require.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/undeclared_external_resource.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/unused_live_view_assign.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/use_oban_pro_worker.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/vacuous_test.ex", + "deps/jump_credo_checks/lib/jump/credo_checks/weak_assertion.ex" + ], # # If you want to enforce a style guide and need a more traditional linting # experience, you can change `strict` to `true` below: @@ -163,7 +184,31 @@ {Credo.Check.Warning.WrongTestFilename, []}, {Credo.Check.Readability.Specs, []}, {Credo.Check.Warning.LeakyEnvironment, []}, - {Credo.Check.Warning.UnsafeToAtom, []} + {Credo.Check.Warning.UnsafeToAtom, []}, + + # + ## Jump Credo Checks + # + {Jump.CredoChecks.AssertElementSelectorCanNeverFail, []}, + {Jump.CredoChecks.AssertReceiveTimeout, []}, + {Jump.CredoChecks.AvoidFunctionLevelElse, []}, + {Jump.CredoChecks.AvoidLoggerConfigureInTest, []}, + {Jump.CredoChecks.AvoidSocketAssignsInTest, []}, + {Jump.CredoChecks.ConditionalAssertion, []}, + {Jump.CredoChecks.DoctestIExExamples, []}, + {Jump.CredoChecks.ForbiddenFunction, []}, + {Jump.CredoChecks.LiveViewFormCanBeRehydrated, []}, + {Jump.CredoChecks.UndeclaredExternalResource, []}, + {Jump.CredoChecks.PreferChangeOverUpDownMigrations, [start_after: "0"]}, + {Jump.CredoChecks.PreferTextColumns, [start_after: "0"]}, + {Jump.CredoChecks.SafeBinaryToTerm, []}, + {Jump.CredoChecks.TestHasNoAssertions, []}, + {Jump.CredoChecks.TooManyAssertions, []}, + {Jump.CredoChecks.TopLevelAliasImportRequire, []}, + {Jump.CredoChecks.UnusedLiveViewAssign, []}, + {Jump.CredoChecks.UseObanProWorker, []}, + {Jump.CredoChecks.VacuousTest, []}, + {Jump.CredoChecks.WeakAssertion, []} ], disabled: [ # diff --git a/format_commands.sh b/format_commands.sh new file mode 100644 index 00000000..b3fb99d4 --- /dev/null +++ b/format_commands.sh @@ -0,0 +1,10 @@ +cd /Users/graham/dev/ntms/microwaveprop && \ +mix format lib/microwaveprop_web/controllers/user_settings_controller.ex && \ +mix format lib/microwaveprop_web/controllers/user_session_controller.ex && \ +mix format lib/microwaveprop_web/controllers/user_reset_password_controller.ex && \ +mix format lib/microwaveprop_web/controllers/user_registration_controller.ex && \ +mix format lib/microwaveprop_web/controllers/page_controller.ex && \ +mix format lib/microwaveprop_web/controllers/health_controller.ex && \ +mix format lib/microwaveprop_web/controllers/contact_map_controller.ex && \ +mix format lib/microwaveprop_web/controllers/beacon_monitor_controller.ex && \ +mix format lib/microwaveprop_web/controllers/api_token_controller.ex diff --git a/lib/microwaveprop/application.ex b/lib/microwaveprop/application.ex index ec204aa3..e0687cf7 100644 --- a/lib/microwaveprop/application.ex +++ b/lib/microwaveprop/application.ex @@ -5,6 +5,8 @@ defmodule Microwaveprop.Application do use Application + alias MicrowavepropWeb.Api.RateLimiter + require Logger @build_timestamp DateTime.utc_now() @@ -13,7 +15,7 @@ defmodule Microwaveprop.Application do def start(_type, _args) do # Eagerly create the API rate limiter's ETS table so the plug never # races to create it on the request path. - :ok = MicrowavepropWeb.Api.RateLimiter.init_table() + :ok = RateLimiter.init_table() topologies = Application.get_env(:libcluster, :topologies, []) diff --git a/lib/microwaveprop/buildings/parser.ex b/lib/microwaveprop/buildings/parser.ex index fdead40e..b94d459d 100644 --- a/lib/microwaveprop/buildings/parser.ex +++ b/lib/microwaveprop/buildings/parser.ex @@ -24,6 +24,7 @@ defmodule Microwaveprop.Buildings.Parser do height is known. Lazy; suitable for tiles with millions of rows. """ @dialyzer {:no_return, parse_tile: 1} + @spec parse_tile(Path.t()) :: Stream.t([building_record()]) def parse_tile(path) do path |> File.stream!([:read, :compressed]) diff --git a/lib/microwaveprop/canopy/bulk_fetch.ex b/lib/microwaveprop/canopy/bulk_fetch.ex index a9e37f4a..e9f34915 100644 --- a/lib/microwaveprop/canopy/bulk_fetch.ex +++ b/lib/microwaveprop/canopy/bulk_fetch.ex @@ -139,7 +139,7 @@ defmodule Microwaveprop.Canopy.BulkFetch do out_path ] - case System.cmd("gdal_translate", args, stderr_to_stdout: true) do + case System.cmd("gdal_translate", args, env: %{}, stderr_to_stdout: true) do {_, 0} -> # Discard ENVI sidecar metadata. Enum.each([".hdr", ".aux.xml"], fn ext -> _ = File.rm(out_path <> ext) end) diff --git a/lib/microwaveprop/commercial/poll_worker.ex b/lib/microwaveprop/commercial/poll_worker.ex index d9f04e0d..ad9d0c6f 100644 --- a/lib/microwaveprop/commercial/poll_worker.ex +++ b/lib/microwaveprop/commercial/poll_worker.ex @@ -1,6 +1,6 @@ defmodule Microwaveprop.Commercial.PollWorker do @moduledoc false - use Oban.Worker, queue: :commercial, max_attempts: 1 + use Oban.Pro.Worker, queue: :commercial, max_attempts: 1 alias Microwaveprop.Commercial alias Microwaveprop.Commercial.SnmpClient diff --git a/lib/microwaveprop/commercial/snmp_client.ex b/lib/microwaveprop/commercial/snmp_client.ex index 1b6ad2f3..ab58030c 100644 --- a/lib/microwaveprop/commercial/snmp_client.ex +++ b/lib/microwaveprop/commercial/snmp_client.ex @@ -142,7 +142,7 @@ defmodule Microwaveprop.Commercial.SnmpClient do end defp default_run_cmd(cmd, args) do - System.cmd(cmd, args, stderr_to_stdout: true) + System.cmd(cmd, args, env: %{}, stderr_to_stdout: true) rescue ErlangError -> {"command not found: #{cmd}", 127} end diff --git a/lib/microwaveprop/propagation/mechanism_classifier.ex b/lib/microwaveprop/propagation/mechanism_classifier.ex index 0378b436..119ba98d 100644 --- a/lib/microwaveprop/propagation/mechanism_classifier.ex +++ b/lib/microwaveprop/propagation/mechanism_classifier.ex @@ -46,6 +46,7 @@ defmodule Microwaveprop.Propagation.MechanismClassifier do """ alias Microwaveprop.Propagation.MoonEphemeris + alias Microwaveprop.Propagation.SporadicE # ADIF PROP_MODE values we recognize. Anything else falls through to # physics-based classification. @@ -244,7 +245,7 @@ defmodule Microwaveprop.Propagation.MechanismClassifier do defp try_sporadic_e(%{foes_mhz: nil}), do: :no_match defp try_sporadic_e(%{foes_mhz: foes, band_mhz: band, distance_km: dist}) do - muf_mhz = Microwaveprop.Propagation.SporadicE.single_hop_muf(foes, dist) + muf_mhz = SporadicE.single_hop_muf(foes, dist) if muf_mhz >= band do confidence = if muf_mhz >= 1.5 * band, do: :high, else: :medium diff --git a/lib/microwaveprop/propagation/path_compute.ex b/lib/microwaveprop/propagation/path_compute.ex index 9363acba..9a4a605d 100644 --- a/lib/microwaveprop/propagation/path_compute.ex +++ b/lib/microwaveprop/propagation/path_compute.ex @@ -76,9 +76,11 @@ defmodule Microwaveprop.Propagation.PathCompute do @total_stages length(@stages) @doc "Total number of progress stages emitted by `compute/5`." + @spec total_stages() :: integer() def total_stages, do: @total_stages @doc "Ordered stage labels emitted by `compute/5` (1-indexed)." + @spec stage_labels() :: list(String.t()) def stage_labels, do: @stages @doc """ @@ -368,20 +370,7 @@ defmodule Microwaveprop.Propagation.PathCompute do defp build_scoring([], _src, _dst, _now, _band_config, _native_duct), do: {nil, nil} defp build_scoring(profiles, src, dst, now, band_config, native_duct) do - {temps, dewpoints, pressures, gradients, bl_depths, pwats} = - Enum.reduce(profiles, {[], [], [], [], [], []}, fn p, {ts, ds, ps, gs, bs, ws} -> - { - if(p.surface_temp_c == nil, do: ts, else: [p.surface_temp_c | ts]), - if(p.surface_dewpoint_c == nil, do: ds, else: [p.surface_dewpoint_c | ds]), - if(p.surface_pressure_mb == nil, do: ps, else: [p.surface_pressure_mb | ps]), - if(p.min_refractivity_gradient == nil, - do: gs, - else: [p.min_refractivity_gradient | gs] - ), - if(p.hpbl_m == nil, do: bs, else: [p.hpbl_m | bs]), - if(p.pwat_mm == nil, do: ws, else: [p.pwat_mm | ws]) - } - end) + {temps, dewpoints, pressures, gradients, bl_depths, pwats} = collect_profile_fields(profiles) if temps == [] or dewpoints == [] do {nil, nil} @@ -389,35 +378,57 @@ defmodule Microwaveprop.Propagation.PathCompute do avg_temp_c = Enum.sum(temps) / length(temps) avg_dewpoint_c = Enum.sum(dewpoints) / length(dewpoints) - conditions = %{ - abs_humidity: Scorer.absolute_humidity(avg_temp_c, avg_dewpoint_c), - temp_f: Scorer.c_to_f(avg_temp_c), - dewpoint_f: Scorer.c_to_f(avg_dewpoint_c), - temp_c: avg_temp_c, - dewpoint_c: avg_dewpoint_c, - wind_speed_kts: nil, - sky_cover_pct: nil, - utc_hour: now.hour, - utc_minute: now.minute, - month: now.month, - latitude: (src.lat + dst.lat) / 2, - longitude: (src.lon + dst.lon) / 2, - pressure_mb: if(pressures != [], do: Enum.min(pressures)), - prev_pressure_mb: nil, - rain_rate_mmhr: 0.0, - min_refractivity_gradient: if(gradients != [], do: Enum.min(gradients)), - bl_depth_m: if(bl_depths != [], do: Enum.sum(bl_depths) / length(bl_depths)), - pwat_mm: if(pwats != [], do: Enum.sum(pwats) / length(pwats)), - best_duct_band_ghz: native_duct[:best_duct_band_ghz], - bulk_richardson: native_duct[:bulk_richardson] - } + conditions = + build_conditions(avg_temp_c, avg_dewpoint_c, src, dst, now, {pressures, gradients, bl_depths, pwats}, native_duct) scoring = Scorer.composite_score(conditions, band_config) {conditions, scoring} end end + defp collect_profile_fields(profiles) do + Enum.reduce(profiles, {[], [], [], [], [], []}, fn p, {ts, ds, ps, gs, bs, ws} -> + { + if(p.surface_temp_c == nil, do: ts, else: [p.surface_temp_c | ts]), + if(p.surface_dewpoint_c == nil, do: ds, else: [p.surface_dewpoint_c | ds]), + if(p.surface_pressure_mb == nil, do: ps, else: [p.surface_pressure_mb | ps]), + if(p.min_refractivity_gradient == nil, + do: gs, + else: [p.min_refractivity_gradient | gs] + ), + if(p.hpbl_m == nil, do: bs, else: [p.hpbl_m | bs]), + if(p.pwat_mm == nil, do: ws, else: [p.pwat_mm | ws]) + } + end) + end + + defp build_conditions(avg_temp_c, avg_dewpoint_c, src, dst, now, {pressures, gradients, bl_depths, pwats}, native_duct) do + %{ + abs_humidity: Scorer.absolute_humidity(avg_temp_c, avg_dewpoint_c), + temp_f: Scorer.c_to_f(avg_temp_c), + dewpoint_f: Scorer.c_to_f(avg_dewpoint_c), + temp_c: avg_temp_c, + dewpoint_c: avg_dewpoint_c, + wind_speed_kts: nil, + sky_cover_pct: nil, + utc_hour: now.hour, + utc_minute: now.minute, + month: now.month, + latitude: (src.lat + dst.lat) / 2, + longitude: (src.lon + dst.lon) / 2, + pressure_mb: if(pressures != [], do: Enum.min(pressures)), + prev_pressure_mb: nil, + rain_rate_mmhr: 0.0, + min_refractivity_gradient: if(gradients != [], do: Enum.min(gradients)), + bl_depth_m: if(bl_depths != [], do: Enum.sum(bl_depths) / length(bl_depths)), + pwat_mm: if(pwats != [], do: Enum.sum(pwats) / length(pwats)), + best_duct_band_ghz: native_duct[:best_duct_band_ghz], + bulk_richardson: native_duct[:bulk_richardson] + } + end + @doc false + @spec compute_loss_budget(float(), float(), map(), map() | nil, map() | nil) :: map() def compute_loss_budget(dist_km, freq_ghz, band_config, terrain_result, conditions) do freq_mhz = freq_ghz * 1000 fspl = 20 * :math.log10(max(dist_km, 0.001)) + 20 * :math.log10(freq_mhz) + 32.44 @@ -462,6 +473,7 @@ defmodule Microwaveprop.Propagation.PathCompute do end @doc false + @spec compute_power_budget(station_params(), map()) :: map() def compute_power_budget(station_params, loss_budget) do tx_power_dbm = station_params.tx_power_dbm eirp_dbm = tx_power_dbm + station_params.src_gain_dbi diff --git a/lib/microwaveprop/radio.ex b/lib/microwaveprop/radio.ex index da17edec..d16b6767 100644 --- a/lib/microwaveprop/radio.ex +++ b/lib/microwaveprop/radio.ex @@ -466,6 +466,7 @@ defmodule Microwaveprop.Radio do defp deg_to_rad(deg), do: deg * :math.pi() / 180 @dialyzer {:no_match, get_contact!: 1} + @spec get_contact!(term()) :: Contact.t() def get_contact!(id) do Contact |> Repo.get!(id) diff --git a/lib/microwaveprop/weather.ex b/lib/microwaveprop/weather.ex index e086cba6..0d8c8c02 100644 --- a/lib/microwaveprop/weather.ex +++ b/lib/microwaveprop/weather.ex @@ -545,24 +545,7 @@ defmodule Microwaveprop.Weather do {values_sql, params} = updates |> Enum.with_index() - |> Enum.reduce({"", []}, fn u, {sql, params} -> - base = Enum.count(params) - frag = "($#{base + 1}::uuid, $#{base + 2}, $#{base + 3}, $#{base + 4}, $#{base + 5}, $#{base + 6})" - sep = if sql == "", do: "", else: ", " - - params = - params ++ - [ - Ecto.UUID.dump!(u.id), - u.surface_refractivity, - u.min_refractivity_gradient, - u.ducting_detected, - u.duct_characteristics || [], - now - ] - - {sql <> sep <> frag, params} - end) + |> Enum.reduce({"", []}, &build_values_row(&1, &2, now)) Repo.query!( "UPDATE hrrr_profiles AS h " <> @@ -582,6 +565,25 @@ defmodule Microwaveprop.Weather do length(updates) end + defp build_values_row({u, idx}, {sql, params}, now) do + base = Enum.count(params) + frag = "($#{base + 1}::uuid, $#{base + 2}, $#{base + 3}, $#{base + 4}, $#{base + 5}, $#{base + 6})" + sep = if sql == "", do: "", else: ", " + + params = + params ++ + [ + Ecto.UUID.dump!(u.id), + u.surface_refractivity, + u.min_refractivity_gradient, + u.ducting_detected, + u.duct_characteristics || [], + now + ] + + {sql <> sep <> frag, params} + end + @doc """ True if the station already has at least one surface observation anywhere within the given UTC date. Used by the `asos_day` worker diff --git a/lib/microwaveprop/weather/grib2/wgrib2.ex b/lib/microwaveprop/weather/grib2/wgrib2.ex index 3e1e996b..4b836b2e 100644 --- a/lib/microwaveprop/weather/grib2/wgrib2.ex +++ b/lib/microwaveprop/weather/grib2/wgrib2.ex @@ -115,7 +115,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do try do args = [grib_path, "-match", match_pattern, "-lola", lon_spec, lat_spec, tmp_bin, "bin"] - case System.cmd(wgrib2_path(), args, stderr_to_stdout: true) do + case System.cmd(wgrib2_path(), args, env: %{}, stderr_to_stdout: true) do {output, 0} -> messages = parse_wgrib2_inventory(output) @@ -152,7 +152,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do try do args = [grib_path, "-match", match_pattern, "-lola", lon_spec, lat_spec, tmp_bin, "bin"] - case System.cmd(wgrib2_path(), args, stderr_to_stdout: true) do + case System.cmd(wgrib2_path(), args, env: %{}, stderr_to_stdout: true) do {output, 0} -> messages = parse_wgrib2_inventory(output) @@ -274,7 +274,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do "bin" ] - case System.cmd(wgrib2_path(), args, stderr_to_stdout: true) do + case System.cmd(wgrib2_path(), args, env: %{}, stderr_to_stdout: true) do {output, 0} -> # Parse message inventory from stdout to know which vars were extracted messages = parse_wgrib2_inventory(output) @@ -421,7 +421,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do try do args = [grib_path, "-match", match_pattern, "-lola", lon_spec, lat_spec, tmp_bin, "bin"] - case System.cmd(wgrib2_path(), args, stderr_to_stdout: true) do + case System.cmd(wgrib2_path(), args, env: %{}, stderr_to_stdout: true) do {output, 0} -> messages = parse_wgrib2_inventory(output) @@ -593,7 +593,7 @@ defmodule Microwaveprop.Weather.Grib2.Wgrib2 do args = [grib_path, "-s", "-match", match_pattern] ++ lon_args - case System.cmd(wgrib2_path(), args, stderr_to_stdout: true) do + case System.cmd(wgrib2_path(), args, env: %{}, stderr_to_stdout: true) do {output, 0} -> {:ok, parse_lon_output(output, points)} diff --git a/lib/microwaveprop/weather/hrrr_point_enqueuer.ex b/lib/microwaveprop/weather/hrrr_point_enqueuer.ex index 1ec3eeaf..796567ff 100644 --- a/lib/microwaveprop/weather/hrrr_point_enqueuer.ex +++ b/lib/microwaveprop/weather/hrrr_point_enqueuer.ex @@ -20,6 +20,8 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do import Ecto.Query alias Microwaveprop.Repo + alias Microwaveprop.Weather.HrrrClient + alias Microwaveprop.Weather.NarrClient require Logger @@ -110,11 +112,6 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do """ @spec enqueue_for_contacts([map()]) :: {:ok, non_neg_integer()} def enqueue_for_contacts(contacts) do - alias Microwaveprop.Radio - alias Microwaveprop.Weather - alias Microwaveprop.Weather.HrrrClient - alias Microwaveprop.Weather.NarrClient - groups = contacts |> Enum.flat_map(&contact_points/1) @@ -129,11 +126,6 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do # HRRR archive starts mid-2014; contacts older than that belong to # NARR, so they're dropped here. defp contact_points(contact) do - alias Microwaveprop.Radio - alias Microwaveprop.Weather - alias Microwaveprop.Weather.HrrrClient - alias Microwaveprop.Weather.NarrClient - cond do is_nil(contact.pos1) -> [] diff --git a/lib/microwaveprop/weather/narr_client.ex b/lib/microwaveprop/weather/narr_client.ex index b389f078..a7bfbb04 100644 --- a/lib/microwaveprop/weather/narr_client.ex +++ b/lib/microwaveprop/weather/narr_client.ex @@ -298,7 +298,7 @@ defmodule Microwaveprop.Weather.NarrClient do inputs = Enum.map(record_paths, fn {_key, path} -> path end) args = ["-merge"] ++ inputs ++ [merged_path] - case System.cmd("cdo", args, stderr_to_stdout: true) do + case System.cmd("cdo", args, env: %{}, stderr_to_stdout: true) do {_output, 0} -> :ok {output, code} -> {:error, "cdo -merge exited #{code}: #{output}"} end @@ -311,7 +311,7 @@ defmodule Microwaveprop.Weather.NarrClient do grb_path ] - case System.cmd("cdo", args, stderr_to_stdout: true) do + case System.cmd("cdo", args, env: %{}, stderr_to_stdout: true) do {stdout, 0} -> parse_cdo_outputtab(stdout) diff --git a/lib/microwaveprop/workers/admin_task_worker.ex b/lib/microwaveprop/workers/admin_task_worker.ex index 408dff1d..654654cc 100644 --- a/lib/microwaveprop/workers/admin_task_worker.ex +++ b/lib/microwaveprop/workers/admin_task_worker.ex @@ -12,7 +12,7 @@ defmodule Microwaveprop.Workers.AdminTaskWorker do Microwaveprop.Workers.AdminTaskWorker.new(%{task: "climatology", min_samples: 3}) Microwaveprop.Workers.AdminTaskWorker.new(%{task: "native_derive", limit: 10000}) """ - use Oban.Worker, queue: :admin, max_attempts: 1, unique: [period: 60] + use Oban.Pro.Worker, queue: :admin, max_attempts: 1, unique: [period: 60] import Ecto.Query diff --git a/lib/microwaveprop/workers/backfill_enqueue_worker.ex b/lib/microwaveprop/workers/backfill_enqueue_worker.ex index 4e212b90..af9bda8d 100644 --- a/lib/microwaveprop/workers/backfill_enqueue_worker.ex +++ b/lib/microwaveprop/workers/backfill_enqueue_worker.ex @@ -3,7 +3,7 @@ defmodule Microwaveprop.Workers.BackfillEnqueueWorker do Runs backfill enrichment enqueue as an Oban job so the backfill dashboard returns immediately instead of blocking on the enqueue loop. """ - use Oban.Worker, queue: :backfill_enqueue, max_attempts: 1 + use Oban.Pro.Worker, queue: :backfill_enqueue, max_attempts: 1 import Ecto.Query diff --git a/lib/microwaveprop/workers/canadian_sounding_fetch_worker.ex b/lib/microwaveprop/workers/canadian_sounding_fetch_worker.ex index 0b97bb5b..b5b00d49 100644 --- a/lib/microwaveprop/workers/canadian_sounding_fetch_worker.ex +++ b/lib/microwaveprop/workers/canadian_sounding_fetch_worker.ex @@ -12,7 +12,7 @@ defmodule Microwaveprop.Workers.CanadianSoundingFetchWorker do and upserts it into `soundings`. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :weather, max_attempts: 3, unique: [period: 3600, states: :incomplete] diff --git a/lib/microwaveprop/workers/common_volume_radar_worker.ex b/lib/microwaveprop/workers/common_volume_radar_worker.ex index 03acd712..ad109827 100644 --- a/lib/microwaveprop/workers/common_volume_radar_worker.ex +++ b/lib/microwaveprop/workers/common_volume_radar_worker.ex @@ -12,7 +12,7 @@ defmodule Microwaveprop.Workers.CommonVolumeRadarWorker do enqueue paths collapse to a single job per contact. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :radar, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/contact_import_worker.ex b/lib/microwaveprop/workers/contact_import_worker.ex index a98d7b26..3241bc8b 100644 --- a/lib/microwaveprop/workers/contact_import_worker.ex +++ b/lib/microwaveprop/workers/contact_import_worker.ex @@ -16,7 +16,7 @@ defmodule Microwaveprop.Workers.ContactImportWorker do that sees `status = 'pending'` on its first increment flips it to `"running"` and sets `started_at`. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :contact_import, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/contact_position_backfill_worker.ex b/lib/microwaveprop/workers/contact_position_backfill_worker.ex index 89220dc4..81062f5f 100644 --- a/lib/microwaveprop/workers/contact_position_backfill_worker.ex +++ b/lib/microwaveprop/workers/contact_position_backfill_worker.ex @@ -7,7 +7,7 @@ defmodule Microwaveprop.Workers.ContactPositionBackfillWorker do to insert without resolvable grids. This worker exists for rows that arrive via direct DB writes (bulk imports, manual fixes) which bypass that path. """ - use Oban.Worker, queue: :admin, max_attempts: 1, unique: [period: 60] + use Oban.Pro.Worker, queue: :admin, max_attempts: 1, unique: [period: 60] import Ecto.Query diff --git a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex index aaf3009a..8d33d10a 100644 --- a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex +++ b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex @@ -1,6 +1,6 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do @moduledoc false - use Oban.Worker, queue: :enqueue, max_attempts: 3 + use Oban.Pro.Worker, queue: :enqueue, max_attempts: 3 alias Microwaveprop.Propagation.Grid alias Microwaveprop.Radio diff --git a/lib/microwaveprop/workers/gefs_fetch_worker.ex b/lib/microwaveprop/workers/gefs_fetch_worker.ex index 190b6477..a34ff36f 100644 --- a/lib/microwaveprop/workers/gefs_fetch_worker.ex +++ b/lib/microwaveprop/workers/gefs_fetch_worker.ex @@ -11,7 +11,7 @@ defmodule Microwaveprop.Workers.GefsFetchWorker do 0.125° grid still carries ~200k cells. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :gefs, max_attempts: 5, unique: [period: 3600, states: :incomplete] diff --git a/lib/microwaveprop/workers/grid_cache_prune_worker.ex b/lib/microwaveprop/workers/grid_cache_prune_worker.ex index 31839ab3..6f633948 100644 --- a/lib/microwaveprop/workers/grid_cache_prune_worker.ex +++ b/lib/microwaveprop/workers/grid_cache_prune_worker.ex @@ -9,7 +9,7 @@ defmodule Microwaveprop.Workers.GridCachePruneWorker do at a lower priority so the hourly grid chain never waits behind it. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :propagation, priority: 5, max_attempts: 3, diff --git a/lib/microwaveprop/workers/hrdps_grid_worker.ex b/lib/microwaveprop/workers/hrdps_grid_worker.ex index 202f77e1..ecb1c9aa 100644 --- a/lib/microwaveprop/workers/hrdps_grid_worker.ex +++ b/lib/microwaveprop/workers/hrdps_grid_worker.ex @@ -28,7 +28,7 @@ defmodule Microwaveprop.Workers.HrdpsGridWorker do enqueuer call back to `GridTaskEnqueuer.seed_with_analysis/2`. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :hrdps, priority: 0, max_attempts: 5, diff --git a/lib/microwaveprop/workers/hrrr_native_grid_worker.ex b/lib/microwaveprop/workers/hrrr_native_grid_worker.ex index 93ec0bec..20e669f7 100644 --- a/lib/microwaveprop/workers/hrrr_native_grid_worker.ex +++ b/lib/microwaveprop/workers/hrrr_native_grid_worker.ex @@ -15,7 +15,7 @@ defmodule Microwaveprop.Workers.HrrrNativeGridWorker do in a single pass. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :hrrr, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/iemre_fetch_worker.ex b/lib/microwaveprop/workers/iemre_fetch_worker.ex index c39e5c99..473845e7 100644 --- a/lib/microwaveprop/workers/iemre_fetch_worker.ex +++ b/lib/microwaveprop/workers/iemre_fetch_worker.ex @@ -1,6 +1,6 @@ defmodule Microwaveprop.Workers.IemreFetchWorker do @moduledoc false - use Oban.Worker, + use Oban.Pro.Worker, queue: :iemre, max_attempts: 20, unique: [period: 300, fields: [:args], states: :scheduled] diff --git a/lib/microwaveprop/workers/ionosphere_fetch_worker.ex b/lib/microwaveprop/workers/ionosphere_fetch_worker.ex index 04214136..f6f0db1b 100644 --- a/lib/microwaveprop/workers/ionosphere_fetch_worker.ex +++ b/lib/microwaveprop/workers/ionosphere_fetch_worker.ex @@ -13,7 +13,7 @@ defmodule Microwaveprop.Workers.IonosphereFetchWorker do idempotent on (station_code, valid_time). """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :ionosphere, max_attempts: 3, unique: [period: 300, states: :incomplete] diff --git a/lib/microwaveprop/workers/mechanism_classify_worker.ex b/lib/microwaveprop/workers/mechanism_classify_worker.ex index 9182376e..2050af13 100644 --- a/lib/microwaveprop/workers/mechanism_classify_worker.ex +++ b/lib/microwaveprop/workers/mechanism_classify_worker.ex @@ -19,7 +19,7 @@ defmodule Microwaveprop.Workers.MechanismClassifyWorker do backfill cron collapse to a single job per contact. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :mechanism, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/narr_fetch_worker.ex b/lib/microwaveprop/workers/narr_fetch_worker.ex index f842954c..4fe3a5ed 100644 --- a/lib/microwaveprop/workers/narr_fetch_worker.ex +++ b/lib/microwaveprop/workers/narr_fetch_worker.ex @@ -17,7 +17,7 @@ defmodule Microwaveprop.Workers.NarrFetchWorker do See `docs/plans/2026-04-15-merra2-historical-backfill.md` for the full architecture. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :narr, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/nexrad_worker.ex b/lib/microwaveprop/workers/nexrad_worker.ex index ce6bf8b9..eb4d4b7b 100644 --- a/lib/microwaveprop/workers/nexrad_worker.ex +++ b/lib/microwaveprop/workers/nexrad_worker.ex @@ -7,7 +7,7 @@ defmodule Microwaveprop.Workers.NexradWorker do sweeps that enqueue duplicate timestamps collapse automatically. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :nexrad, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/partition_maintenance_worker.ex b/lib/microwaveprop/workers/partition_maintenance_worker.ex index 3bcd48f8..33fc91ae 100644 --- a/lib/microwaveprop/workers/partition_maintenance_worker.ex +++ b/lib/microwaveprop/workers/partition_maintenance_worker.ex @@ -16,7 +16,7 @@ defmodule Microwaveprop.Workers.PartitionMaintenanceWorker do cost of an extra empty partition is one zero-row table, while the cost of a missing partition is silently failed inserts. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :admin, max_attempts: 3, unique: [period: 3600, states: :incomplete] diff --git a/lib/microwaveprop/workers/propagation_grid_worker.ex b/lib/microwaveprop/workers/propagation_grid_worker.ex index 567fa8e6..dcbfc8e5 100644 --- a/lib/microwaveprop/workers/propagation_grid_worker.ex +++ b/lib/microwaveprop/workers/propagation_grid_worker.ex @@ -16,7 +16,7 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do chain step, through the PropagationNotifyListener path. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :propagation, priority: 0, max_attempts: 5, diff --git a/lib/microwaveprop/workers/propagation_prune_worker.ex b/lib/microwaveprop/workers/propagation_prune_worker.ex index ad0fba9b..9d552ef8 100644 --- a/lib/microwaveprop/workers/propagation_prune_worker.ex +++ b/lib/microwaveprop/workers/propagation_prune_worker.ex @@ -9,7 +9,7 @@ defmodule Microwaveprop.Workers.PropagationPruneWorker do regardless of the compute worker's state. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :propagation, # Lower priority than PropagationGridWorker so the hourly chain # never waits behind a pending prune on the shared :propagation diff --git a/lib/microwaveprop/workers/pskr_calibration_worker.ex b/lib/microwaveprop/workers/pskr_calibration_worker.ex index 2a37b2f2..169e9a15 100644 --- a/lib/microwaveprop/workers/pskr_calibration_worker.ex +++ b/lib/microwaveprop/workers/pskr_calibration_worker.ex @@ -46,7 +46,7 @@ defmodule Microwaveprop.Workers.PskrCalibrationWorker do lag for the current hour); the rolling-window cron remains the safety net. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :backfill_enqueue, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/pskr_recalibration_worker.ex b/lib/microwaveprop/workers/pskr_recalibration_worker.ex index 0f536b50..7f04a1b7 100644 --- a/lib/microwaveprop/workers/pskr_recalibration_worker.ex +++ b/lib/microwaveprop/workers/pskr_recalibration_worker.ex @@ -14,7 +14,7 @@ defmodule Microwaveprop.Workers.PskrRecalibrationWorker do produces a fresh run record; previous runs stay queryable for trend comparison. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :backfill_enqueue, max_attempts: 3, unique: [ diff --git a/lib/microwaveprop/workers/radar_frame_worker.ex b/lib/microwaveprop/workers/radar_frame_worker.ex index f2c63c4b..ab2cf06b 100644 --- a/lib/microwaveprop/workers/radar_frame_worker.ex +++ b/lib/microwaveprop/workers/radar_frame_worker.ex @@ -18,7 +18,7 @@ defmodule Microwaveprop.Workers.RadarFrameWorker do # unique on frame_ts: backfill sweeps that re-select the same still-:queued # contacts each cycle would otherwise enqueue a fresh 1-job-per-contact # frame fetch. 9k+ wasteful duplicates observed in prod before this guard. - use Oban.Worker, + use Oban.Pro.Worker, queue: :radar, max_attempts: 3, unique: [fields: [:args], keys: [:frame_ts], states: :incomplete, period: :infinity] diff --git a/lib/microwaveprop/workers/rover_mission_backfill_worker.ex b/lib/microwaveprop/workers/rover_mission_backfill_worker.ex index e64e6582..5df6aba3 100644 --- a/lib/microwaveprop/workers/rover_mission_backfill_worker.ex +++ b/lib/microwaveprop/workers/rover_mission_backfill_worker.ex @@ -11,7 +11,7 @@ defmodule Microwaveprop.Workers.RoverMissionBackfillWorker do without operator action. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :terrain, priority: 5, max_attempts: 3, diff --git a/lib/microwaveprop/workers/rover_mission_reconcile_worker.ex b/lib/microwaveprop/workers/rover_mission_reconcile_worker.ex index e744adf9..2b1a4343 100644 --- a/lib/microwaveprop/workers/rover_mission_reconcile_worker.ex +++ b/lib/microwaveprop/workers/rover_mission_reconcile_worker.ex @@ -13,7 +13,7 @@ defmodule Microwaveprop.Workers.RoverMissionReconcileWorker do tuples that aren't already represented. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :terrain, priority: 3, max_attempts: 3, diff --git a/lib/microwaveprop/workers/rover_path_profile_worker.ex b/lib/microwaveprop/workers/rover_path_profile_worker.ex index e5f90152..0b5c7f70 100644 --- a/lib/microwaveprop/workers/rover_path_profile_worker.ex +++ b/lib/microwaveprop/workers/rover_path_profile_worker.ex @@ -15,7 +15,7 @@ defmodule Microwaveprop.Workers.RoverPathProfileWorker do `:erlang.term_to_binary/1` + Base64. PathLive decodes this when handed `?rover_path_id=UUID` and renders without recomputing. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :rover_path, max_attempts: 5, unique: [ diff --git a/lib/microwaveprop/workers/solar_index_worker.ex b/lib/microwaveprop/workers/solar_index_worker.ex index c480863b..4ca7cad2 100644 --- a/lib/microwaveprop/workers/solar_index_worker.ex +++ b/lib/microwaveprop/workers/solar_index_worker.ex @@ -1,6 +1,6 @@ defmodule Microwaveprop.Workers.SolarIndexWorker do @moduledoc false - use Oban.Worker, + use Oban.Pro.Worker, queue: :solar, max_attempts: 3, unique: [period: 300, states: :incomplete] diff --git a/lib/microwaveprop/workers/space_weather_fetch_worker.ex b/lib/microwaveprop/workers/space_weather_fetch_worker.ex index 4a4c34f1..d1c2c282 100644 --- a/lib/microwaveprop/workers/space_weather_fetch_worker.ex +++ b/lib/microwaveprop/workers/space_weather_fetch_worker.ex @@ -12,7 +12,7 @@ defmodule Microwaveprop.Workers.SpaceWeatherFetchWorker do fetch is independent: a failure in one product doesn't block the others. """ - use Oban.Worker, + use Oban.Pro.Worker, queue: :space_weather, max_attempts: 3, unique: [period: 120, states: :incomplete] diff --git a/lib/microwaveprop/workers/station_elevation_worker.ex b/lib/microwaveprop/workers/station_elevation_worker.ex index cd8f4c88..106348a8 100644 --- a/lib/microwaveprop/workers/station_elevation_worker.ex +++ b/lib/microwaveprop/workers/station_elevation_worker.ex @@ -8,7 +8,7 @@ defmodule Microwaveprop.Workers.StationElevationWorker do with `elevation_m = nil`. The worker also no-ops if the station has been deleted between enqueue and execution. """ - use Oban.Worker, queue: :terrain, max_attempts: 3 + use Oban.Pro.Worker, queue: :terrain, max_attempts: 3 alias Microwaveprop.Repo alias Microwaveprop.Rover.FixedStation diff --git a/lib/microwaveprop/workers/terrain_profile_worker.ex b/lib/microwaveprop/workers/terrain_profile_worker.ex index d496a138..34d9e822 100644 --- a/lib/microwaveprop/workers/terrain_profile_worker.ex +++ b/lib/microwaveprop/workers/terrain_profile_worker.ex @@ -1,6 +1,6 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do @moduledoc false - use Oban.Worker, + use Oban.Pro.Worker, queue: :terrain, max_attempts: 20, unique: [period: 300, states: :incomplete] diff --git a/lib/microwaveprop/workers/user_home_qth_lookup_worker.ex b/lib/microwaveprop/workers/user_home_qth_lookup_worker.ex index e6a5772a..ee838128 100644 --- a/lib/microwaveprop/workers/user_home_qth_lookup_worker.ex +++ b/lib/microwaveprop/workers/user_home_qth_lookup_worker.ex @@ -16,7 +16,7 @@ defmodule Microwaveprop.Workers.UserHomeQthLookupWorker do end) """ - use Oban.Worker, queue: :weather, max_attempts: 3 + use Oban.Pro.Worker, queue: :weather, max_attempts: 3 alias Microwaveprop.Accounts alias Microwaveprop.Accounts.User diff --git a/lib/microwaveprop/workers/weather_fetch_worker.ex b/lib/microwaveprop/workers/weather_fetch_worker.ex index 4dc7fd92..897daba3 100644 --- a/lib/microwaveprop/workers/weather_fetch_worker.ex +++ b/lib/microwaveprop/workers/weather_fetch_worker.ex @@ -1,6 +1,6 @@ defmodule Microwaveprop.Workers.WeatherFetchWorker do @moduledoc false - use Oban.Worker, + use Oban.Pro.Worker, queue: :weather, max_attempts: 20, unique: [period: :infinity, states: :incomplete] diff --git a/lib/microwaveprop_web/api/fallback_controller.ex b/lib/microwaveprop_web/api/fallback_controller.ex index da575696..41d9d129 100644 --- a/lib/microwaveprop_web/api/fallback_controller.ex +++ b/lib/microwaveprop_web/api/fallback_controller.ex @@ -9,6 +9,7 @@ defmodule MicrowavepropWeb.Api.FallbackController do alias MicrowavepropWeb.Api.ErrorJSON + @spec call(Plug.Conn.t(), term()) :: Plug.Conn.t() def call(conn, {:error, %Ecto.Changeset{} = changeset}) do ErrorJSON.send_changeset(conn, changeset) end diff --git a/lib/microwaveprop_web/components/core_components.ex b/lib/microwaveprop_web/components/core_components.ex index 6b6c9c05..6253f059 100644 --- a/lib/microwaveprop_web/components/core_components.ex +++ b/lib/microwaveprop_web/components/core_components.ex @@ -31,6 +31,7 @@ defmodule MicrowavepropWeb.CoreComponents do alias Phoenix.HTML.Form alias Phoenix.HTML.FormField alias Phoenix.LiveView.JS + alias Phoenix.LiveView.Rendered @doc """ Renders flash notices. @@ -48,6 +49,7 @@ defmodule MicrowavepropWeb.CoreComponents do slot :inner_block, doc: "the optional inner block that renders the flash message" + @spec flash(map()) :: Rendered.t() def flash(assigns) do assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) @@ -94,6 +96,7 @@ defmodule MicrowavepropWeb.CoreComponents do attr :variant, :string, values: ~w(primary) slot :inner_block, required: true + @spec button(map()) :: Rendered.t() def button(%{rest: rest} = assigns) do variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"} @@ -180,6 +183,7 @@ defmodule MicrowavepropWeb.CoreComponents do attr :rest, :global, include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength multiple pattern placeholder readonly required rows size step) + @spec input(map()) :: Rendered.t() def input(%{field: %FormField{} = field} = assigns) do errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] @@ -317,6 +321,7 @@ defmodule MicrowavepropWeb.CoreComponents do slot :subtitle slot :actions + @spec header(map()) :: Rendered.t() def header(assigns) do ~H"""
@@ -339,8 +344,8 @@ defmodule MicrowavepropWeb.CoreComponents do ## Examples <.table id="users" rows={@users}> - <:col :let={user} label="id">{user.id} - <:col :let={user} label="username">{user.username} + <:col :let={row} label="id">{row.id} + <:col :let={row} label="username">{row.username} """ attr :id, :string, required: true @@ -363,6 +368,7 @@ defmodule MicrowavepropWeb.CoreComponents do slot :action, doc: "the slot for showing user actions in the last table column" + @spec table(map()) :: Rendered.t() def table(assigns) do assigns = with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do @@ -440,6 +446,7 @@ defmodule MicrowavepropWeb.CoreComponents do attr :title, :string, required: true end + @spec list(map()) :: Rendered.t() def list(assigns) do ~H"""