defmodule Microwaveprop.Rover.LinkMargin do @moduledoc """ Per-link SNR-margin math for the rover planner. Maps a propagation grid score (0-100) into a predicted SNR using the linear calibration `(score − 50) × 0.5`, then subtracts the mode's required SNR threshold to yield "dB above decode". """ alias Microwaveprop.Propagation.ModeThresholds alias Microwaveprop.Propagation.ScoresFile @spec score_to_db(integer() | nil) :: float() | nil def score_to_db(nil), do: nil def score_to_db(score) when is_integer(score), do: (score - 50) * 0.5 @doc """ Pure helper: compute the link margin from an already-resolved grid score and a mode atom. Used by tests and `Rover.Compute`, which fetches the score itself. """ @spec link_margin_from_score(integer() | nil, ModeThresholds.mode()) :: float() | nil def link_margin_from_score(nil, _mode), do: nil def link_margin_from_score(score, mode) when is_integer(score) do score_to_db(score) - ModeThresholds.required_snr_db(mode) end @doc """ Look up the score for the candidate cell and return the link margin. """ @spec link_margin_db( non_neg_integer(), DateTime.t(), {float(), float()}, {float(), float()}, ModeThresholds.mode() ) :: float() | nil def link_margin_db(band_mhz, %DateTime{} = valid_time, {c_lat, c_lon}, {_s_lat, _s_lon}, mode) do case ScoresFile.read_point(band_mhz, valid_time, c_lat, c_lon) do nil -> nil score -> link_margin_from_score(score, mode) end end end