Move ML deps to dev/test only, exclude from production build

- Nx, Axon, EXLA, Polaris deps restricted to only: [:dev, :test]
- model.ex and training mix tasks moved to lib_ml/ (compiled via
  elixirc_paths in dev/test only)
- load_ml_model uses Code.ensure_loaded? + apply/3 to avoid
  compile-time references to ML modules in production
- Verified: MIX_ENV=prod compiles clean with no ML warnings
This commit is contained in:
Graham McIntire 2026-04-01 12:10:31 -05:00
parent 254e64dedc
commit 2c16fd979d
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
7 changed files with 29 additions and 18 deletions

View file

@ -86,6 +86,9 @@ config :microwaveprop, Oban,
# Enable dev routes for dashboard and mailbox
config :microwaveprop, dev_routes: true
# Load ML model at startup (Nx/Axon/EXLA only available in dev/test)
config :microwaveprop, load_ml_model: true
# Use local SRTM1 tiles for elevation lookups instead of the Open-Meteo API
config :microwaveprop, srtm_tiles_dir: Path.expand("~/srtm/tiles")

View file

@ -25,8 +25,10 @@ defmodule Microwaveprop.Application do
opts = [strategy: :one_for_one, name: Microwaveprop.Supervisor]
result = Supervisor.start_link(children, opts)
# Load ML model after supervision tree is up (non-blocking, no-op if file missing)
Microwaveprop.Propagation.load_ml_model()
# Load ML model in dev/test only (Nx/Axon not compiled for prod)
if Application.get_env(:microwaveprop, :load_ml_model, false) do
Microwaveprop.Propagation.load_ml_model()
end
result
end

View file

@ -6,7 +6,6 @@ defmodule Microwaveprop.Propagation do
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Propagation.Grid
alias Microwaveprop.Propagation.GridScore
alias Microwaveprop.Propagation.Model
alias Microwaveprop.Propagation.Scorer
alias Microwaveprop.Repo
alias Microwaveprop.Weather.SoundingParams
@ -14,22 +13,28 @@ defmodule Microwaveprop.Propagation do
require Logger
@ml_key :propagation_ml
@ml_module Microwaveprop.Propagation.Model
@doc """
Loads the ML model from disk, compiles the predict function, and caches both
in persistent_term. No-op if the model file doesn't exist.
in persistent_term. No-op if the model file doesn't exist or ML deps unavailable.
"""
def load_ml_model do
case Model.load() do
{:ok, params} ->
predict_fn = Model.compile_predict()
:persistent_term.put(@ml_key, {predict_fn, params})
Logger.info("PropagationML: model loaded and compiled from #{inspect(Model.default_path())}")
:ok
if Code.ensure_loaded?(@ml_module) do
case apply(@ml_module, :load, []) do
{:ok, params} ->
predict_fn = apply(@ml_module, :compile_predict, [])
:persistent_term.put(@ml_key, {predict_fn, params})
Logger.info("PropagationML: model loaded and compiled")
:ok
:error ->
Logger.info("PropagationML: no model file found, using algorithm scorer only")
:ok
:error ->
Logger.info("PropagationML: no model file found, using algorithm scorer only")
:ok
end
else
Logger.info("PropagationML: ML dependencies not available")
:ok
end
end

11
mix.exs
View file

@ -32,7 +32,8 @@ defmodule Microwaveprop.MixProject do
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(:test), do: ["lib", "lib_ml", "test/support"]
defp elixirc_paths(:dev), do: ["lib", "lib_ml"]
defp elixirc_paths(_), do: ["lib"]
# Specifies your project dependencies.
@ -65,10 +66,10 @@ defmodule Microwaveprop.MixProject do
{:oban, "~> 2.19"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:earmark, "~> 1.4"},
{:nx, "~> 0.9"},
{:axon, "~> 0.7"},
{:exla, "~> 0.9"},
{:polaris, "~> 0.1"}
{:nx, "~> 0.9", only: [:dev, :test]},
{:axon, "~> 0.7", only: [:dev, :test]},
{:exla, "~> 0.9", only: [:dev, :test]},
{:polaris, "~> 0.1", only: [:dev, :test]}
]
end