diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex
index 32bd1fcc..26116642 100644
--- a/lib/microwaveprop_web/live/contact_live/show.ex
+++ b/lib/microwaveprop_web/live/contact_live/show.ex
@@ -38,6 +38,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
contact = maybe_enqueue_weather(weather, contact)
elevation_profile = compute_elevation_profile(contact, hrrr_path, weather.soundings)
propagation_analysis = build_propagation_analysis(contact, hrrr, terrain, elevation_profile, weather.soundings)
+ data_sources = build_data_sources(hrrr, hrrr_path, terrain, weather, elevation_profile)
{:ok,
assign(socket,
@@ -50,6 +51,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
terrain: terrain,
elevation_profile: elevation_profile,
propagation_analysis: propagation_analysis,
+ data_sources: data_sources,
terrain_expanded: false,
hrrr_profile_expanded: false,
obs_sort_by: "station_name",
@@ -776,6 +778,84 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<% end %>
+
+ <%= if @data_sources do %>
+
+
+
HRRR Model
+ <%= if @data_sources.hrrr do %>
+
{@data_sources.hrrr.profile_count} path profiles
+
{@data_sources.hrrr.levels} pressure levels each
+
Valid: {@data_sources.hrrr.valid_time}
+
Grid: {@data_sources.hrrr.lat}, {@data_sources.hrrr.lon}
+ <% else %>
+
Not available
+ <% end %>
+
+
+
+
Elevation Profile
+ <%= if @data_sources.elevation do %>
+
{@data_sources.elevation.samples} samples along path
+
{@data_sources.elevation.dist} path distance
+
Source: {@data_sources.elevation.source}
+
{@data_sources.elevation.duct_count} duct layers detected
+ <% else %>
+
Not available
+ <% end %>
+
+
+
+
Terrain Analysis
+ <%= if @data_sources.terrain do %>
+
{@data_sources.terrain.samples} profile samples
+
Verdict: {@data_sources.terrain.verdict}
+
Max elevation: {@data_sources.terrain.max_elev} m
+
Diffraction: {@data_sources.terrain.diffraction} dB
+ <% else %>
+
Not available
+ <% end %>
+
+
+
+
Surface Observations
+
{length(@surface_observations)} stations
+ <%= if @surface_observations != [] do %>
+
+ {Calendar.strftime(hd(@surface_observations).observed_at, "%H:%M")} –
+ {Calendar.strftime(List.last(@surface_observations).observed_at, "%H:%M")} UTC
+
+ <% end %>
+
+
+
+
Soundings (RAOB)
+
{length(@soundings)} profiles
+ <%= if @soundings != [] do %>
+
+ {Enum.map_join(@soundings, ", ", fn s ->
+ "#{s.station.station_code} (#{s.level_count} levels)"
+ end)}
+
+
{Enum.count(@soundings, & &1.ducting_detected)} with ducting
+ <% end %>
+
+
+
+
Solar Indices
+ <%= if @solar do %>
+
SFI: {@solar.sfi || "—"}
+
SSN: {@solar.sunspot_number || "—"}
+
Ap: {@solar.ap_index || "—"}
+ <% else %>
+
Not available
+ <% end %>
+
+
+ <% end %>
"""
end
@@ -1081,6 +1161,54 @@ defmodule MicrowavepropWeb.ContactLive.Show do
defp rad_to_deg(rad), do: rad * 180 / :math.pi()
defp rem_float(a, b), do: a - Float.floor(a / b) * b
+ # ── Data sources summary ────────────────────────────────────────
+
+ defp build_data_sources(hrrr, hrrr_path, terrain, weather, elevation_profile) do
+ %{
+ hrrr: build_hrrr_source(hrrr, hrrr_path),
+ elevation: build_elevation_source(elevation_profile),
+ terrain: build_terrain_source(terrain),
+ obs_count: length(weather.surface_observations),
+ sounding_count: length(weather.soundings)
+ }
+ end
+
+ defp build_hrrr_source(nil, _), do: nil
+
+ defp build_hrrr_source(hrrr, hrrr_path) do
+ levels = if hrrr.profile, do: length(hrrr.profile), else: 0
+
+ %{
+ profile_count: length(hrrr_path),
+ levels: levels,
+ valid_time: Calendar.strftime(hrrr.valid_time, "%Y-%m-%d %H:%M UTC"),
+ lat: :erlang.float_to_binary(hrrr.lat / 1, decimals: 2),
+ lon: :erlang.float_to_binary(hrrr.lon / 1, decimals: 2)
+ }
+ end
+
+ defp build_elevation_source(nil), do: nil
+
+ defp build_elevation_source(ep) do
+ %{
+ samples: length(ep.points),
+ dist: format_dist(ep.dist_km),
+ source: "SRTM 1-arcsecond",
+ duct_count: length(ep.ducts || [])
+ }
+ end
+
+ defp build_terrain_source(nil), do: nil
+
+ defp build_terrain_source(terrain) do
+ %{
+ samples: terrain.sample_count,
+ verdict: terrain.verdict,
+ max_elev: :erlang.float_to_binary((terrain.max_elevation_m || 0) / 1, decimals: 0),
+ diffraction: :erlang.float_to_binary((terrain.diffraction_db || 0) / 1, decimals: 1)
+ }
+ end
+
# ── Propagation analysis ──────────────────────────────────────────
defp build_propagation_analysis(contact, hrrr, terrain, elevation_profile, soundings) do