diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 4b30a26a..ad373ef0 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -225,10 +225,13 @@ defmodule MicrowavepropWeb.ContactLive.Show do <% end %> <:actions> - @@ -257,9 +260,9 @@ defmodule MicrowavepropWeb.ContactLive.Show do <%= if @elevation_profile do %>
- {format_band_ghz(@contact.band)} · {@contact.mode} - · {format_dist(@elevation_profile.dist_km)} - · {@contact.grid1 || "—"} → {@contact.grid2 || "—"} + {format_band_ghz(@contact.band)} · {@contact.mode} · {format_dist( + @elevation_profile.dist_km + )} · {@contact.grid1 || "—"} → {@contact.grid2 || "—"}
@@ -728,14 +731,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp extract_ducts(hrrr_path, soundings, surface_elev) do hrrr_ducts = extract_hrrr_ducts(hrrr_path, surface_elev) - if hrrr_ducts != [] do - hrrr_ducts - else + if hrrr_ducts == [] do sounding_ducts = extract_sounding_ducts(soundings, surface_elev) - if sounding_ducts != [] do - sounding_ducts - else + if sounding_ducts == [] do # Use the profile with the strongest (most negative) gradient best = hrrr_path @@ -743,7 +742,11 @@ defmodule MicrowavepropWeb.ContactLive.Show do |> Enum.min_by(& &1.min_refractivity_gradient, fn -> nil end) infer_duct_from_gradient(best, surface_elev) + else + sounding_ducts end + else + hrrr_ducts end end @@ -1015,9 +1018,15 @@ defmodule MicrowavepropWeb.ContactLive.Show do verdict = (elevation_profile && elevation_profile.verdict) || (terrain && terrain.verdict) case verdict do - "CLEAR" -> "Line of sight is clear between stations." - "FRESNEL_MINOR" -> "Line of sight is clear but with minor Fresnel zone encroachment." - "FRESNEL_PARTIAL" -> "Line of sight has partial Fresnel zone obstruction." + "CLEAR" -> + "Line of sight is clear between stations." + + "FRESNEL_MINOR" -> + "Line of sight is clear but with minor Fresnel zone encroachment." + + "FRESNEL_PARTIAL" -> + "Line of sight has partial Fresnel zone obstruction." + "BLOCKED" -> obs_dist = elevation_profile && elevation_profile.first_obstruction_km @@ -1027,17 +1036,21 @@ defmodule MicrowavepropWeb.ContactLive.Show do "Path is terrain-obstructed." end - _ -> nil + _ -> + nil end end defp propagation_mechanism(terrain, elevation_profile, hrrr, soundings, dist_km) do blocked? = terrain && terrain.verdict == "BLOCKED" ducting? = has_ducting?(hrrr, soundings) - enhanced_refraction? = hrrr != nil && is_number(hrrr.min_refractivity_gradient) && hrrr.min_refractivity_gradient < -100 + + enhanced_refraction? = + hrrr != nil && is_number(hrrr.min_refractivity_gradient) && hrrr.min_refractivity_gradient < -100 + long_path? = dist_km && dist_km > 100 - ducts = elevation_profile && elevation_profile.ducts || [] + ducts = (elevation_profile && elevation_profile.ducts) || [] likely_duct = Enum.find(ducts, & &1[:likely]) cond do @@ -1049,10 +1062,12 @@ defmodule MicrowavepropWeb.ContactLive.Show do blocked? && enhanced_refraction? -> grad = round(hrrr.min_refractivity_gradient) + "Enhanced refraction (dN/dh = #{grad} N-units/km) likely bent the signal over the obstruction. Conditions are super-refractive but below full ducting threshold." blocked? -> diff_db = terrain && terrain.diffraction_db + if diff_db && diff_db > 0 do "Path is obstructed with #{:erlang.float_to_binary(diff_db, decimals: 1)} dB diffraction loss. Contact may have been enabled by knife-edge diffraction or tropospheric scatter." else @@ -1076,11 +1091,14 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp duct_description(%{source: source, base_m_msl: base, top_m_msl: top, strength: strength}) do base_ft = round(base * 3.28084) top_ft = round(top * 3.28084) - src = case source do - "sounding" -> "sounding-detected" - "inferred" -> "estimated" - _ -> "detected" - end + + src = + case source do + "sounding" -> "sounding-detected" + "inferred" -> "estimated" + _ -> "detected" + end + "#{src} layer at #{base_ft}-#{top_ft} ft, #{strength} M-units" end @@ -1095,16 +1113,12 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp band_summary(band_mhz, hrrr) when band_mhz <= 12_000 do if hrrr && is_number(hrrr.pwat_mm) && hrrr.pwat_mm > 20 do "At 10 GHz, the elevated moisture (PWAT #{:erlang.float_to_binary(hrrr.pwat_mm, decimals: 1)} mm) enhances refractivity and ducting potential." - else - nil end end defp band_summary(band_mhz, hrrr) when band_mhz >= 24_000 do if hrrr && is_number(hrrr.pwat_mm) && hrrr.pwat_mm > 25 do "At #{round(band_mhz / 1000)} GHz, high moisture (PWAT #{:erlang.float_to_binary(hrrr.pwat_mm, decimals: 1)} mm) causes significant water vapor absorption." - else - nil end end @@ -1116,12 +1130,15 @@ defmodule MicrowavepropWeb.ContactLive.Show do details = if hrrr && is_number(hrrr.min_refractivity_gradient) do grad = round(hrrr.min_refractivity_gradient) - label = cond do - grad < -157 -> "ducting" - grad < -100 -> "super-refractive" - grad < -79 -> "enhanced" - true -> "normal" - end + + label = + cond do + grad < -157 -> "ducting" + grad < -100 -> "super-refractive" + grad < -79 -> "enhanced" + true -> "normal" + end + details ++ ["Refractivity gradient: #{grad} N-units/km (#{label})."] else details @@ -1139,11 +1156,11 @@ defmodule MicrowavepropWeb.ContactLive.Show do ducting_soundings = if is_list(soundings), do: Enum.filter(soundings, & &1.ducting_detected), else: [] details = - if ducting_soundings != [] do + if ducting_soundings == [] do + details + else names = Enum.map_join(ducting_soundings, ", ", fn s -> s.station.name || s.station.station_code end) details ++ ["Ducting detected by soundings at: #{names}."] - else - details end details = @@ -1174,24 +1191,30 @@ defmodule MicrowavepropWeb.ContactLive.Show do solar_hour = ts.hour + ts.minute / 60 + (lon || 0) / 15 solar_hour = rem_float(solar_hour + 24, 24) h = round(solar_hour) - label = cond do - h >= 4 and h < 8 -> "dawn" - h >= 8 and h < 12 -> "morning" - h >= 12 and h < 17 -> "afternoon" - h >= 17 and h < 21 -> "evening" - true -> "night" - end + + label = + cond do + h >= 4 and h < 8 -> "dawn" + h >= 8 and h < 12 -> "morning" + h >= 12 and h < 17 -> "afternoon" + h >= 17 and h < 21 -> "evening" + true -> "night" + end + "Solar hour ~#{h} (#{label})" end defp td_note(temp_f, dewpoint_f) when is_number(temp_f) and is_number(dewpoint_f) do depression = Float.round(temp_f - dewpoint_f, 1) - label = cond do - depression < 3 -> "near saturation" - depression < 8 -> "moist" - depression < 15 -> "moderate" - true -> "dry" - end + + label = + cond do + depression < 3 -> "near saturation" + depression < 8 -> "moist" + depression < 15 -> "moderate" + true -> "dry" + end + "T-Td = #{depression}°F (#{label})" end @@ -1201,12 +1224,15 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp refractivity_note(grad) do g = round(grad) - label = cond do - g < -157 -> "ducting" - g < -100 -> "super-refractive" - g < -79 -> "enhanced" - true -> "normal" - end + + label = + cond do + g < -157 -> "ducting" + g < -100 -> "super-refractive" + g < -79 -> "enhanced" + true -> "normal" + end + "dN/dh = #{g} (#{label})" end @@ -1224,12 +1250,15 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp wind_note(kts) do k = Float.round(kts, 1) - label = cond do - k < 5 -> "calm" - k < 15 -> "light" - k < 25 -> "moderate" - true -> "strong" - end + + label = + cond do + k < 5 -> "calm" + k < 15 -> "light" + k < 25 -> "moderate" + true -> "strong" + end + "#{k} kts (#{label})" end @@ -1246,11 +1275,13 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp pressure_note(nil), do: "—" defp pressure_note(mb) do - label = cond do - mb < 1005 -> "low" - mb < 1015 -> "moderate" - true -> "high" - end + label = + cond do + mb < 1005 -> "low" + mb < 1015 -> "moderate" + true -> "high" + end + "#{Float.round(mb, 1)} mb (#{label})" end