Add Data Sources section to contact detail page
Shows provenance of all data used in the analysis: - HRRR: path profile count, pressure levels, valid time, grid point - Elevation: sample count, path distance, source (SRTM), duct layers - Terrain: sample count, verdict, max elevation, diffraction loss - Surface observations: station count and time range - Soundings: profile count, stations, levels, ducting detection - Solar indices: SFI, SSN, Ap
This commit is contained in:
parent
2452f1f397
commit
faa23a9693
1 changed files with 128 additions and 0 deletions
|
|
@ -38,6 +38,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
contact = maybe_enqueue_weather(weather, contact)
|
contact = maybe_enqueue_weather(weather, contact)
|
||||||
elevation_profile = compute_elevation_profile(contact, hrrr_path, weather.soundings)
|
elevation_profile = compute_elevation_profile(contact, hrrr_path, weather.soundings)
|
||||||
propagation_analysis = build_propagation_analysis(contact, hrrr, terrain, elevation_profile, 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,
|
{:ok,
|
||||||
assign(socket,
|
assign(socket,
|
||||||
|
|
@ -50,6 +51,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
terrain: terrain,
|
terrain: terrain,
|
||||||
elevation_profile: elevation_profile,
|
elevation_profile: elevation_profile,
|
||||||
propagation_analysis: propagation_analysis,
|
propagation_analysis: propagation_analysis,
|
||||||
|
data_sources: data_sources,
|
||||||
terrain_expanded: false,
|
terrain_expanded: false,
|
||||||
hrrr_profile_expanded: false,
|
hrrr_profile_expanded: false,
|
||||||
obs_sort_by: "station_name",
|
obs_sort_by: "station_name",
|
||||||
|
|
@ -776,6 +778,84 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%= if @data_sources do %>
|
||||||
|
<div class="divider" />
|
||||||
|
|
||||||
|
<h2 class="text-base font-semibold mb-2">Data Sources</h2>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 text-xs">
|
||||||
|
<div class="bg-base-200 rounded-box p-3">
|
||||||
|
<div class="font-semibold mb-1">HRRR Model</div>
|
||||||
|
<%= if @data_sources.hrrr do %>
|
||||||
|
<div>{@data_sources.hrrr.profile_count} path profiles</div>
|
||||||
|
<div>{@data_sources.hrrr.levels} pressure levels each</div>
|
||||||
|
<div>Valid: {@data_sources.hrrr.valid_time}</div>
|
||||||
|
<div>Grid: {@data_sources.hrrr.lat}, {@data_sources.hrrr.lon}</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="opacity-50">Not available</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-200 rounded-box p-3">
|
||||||
|
<div class="font-semibold mb-1">Elevation Profile</div>
|
||||||
|
<%= if @data_sources.elevation do %>
|
||||||
|
<div>{@data_sources.elevation.samples} samples along path</div>
|
||||||
|
<div>{@data_sources.elevation.dist} path distance</div>
|
||||||
|
<div>Source: {@data_sources.elevation.source}</div>
|
||||||
|
<div>{@data_sources.elevation.duct_count} duct layers detected</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="opacity-50">Not available</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-200 rounded-box p-3">
|
||||||
|
<div class="font-semibold mb-1">Terrain Analysis</div>
|
||||||
|
<%= if @data_sources.terrain do %>
|
||||||
|
<div>{@data_sources.terrain.samples} profile samples</div>
|
||||||
|
<div>Verdict: {@data_sources.terrain.verdict}</div>
|
||||||
|
<div>Max elevation: {@data_sources.terrain.max_elev} m</div>
|
||||||
|
<div>Diffraction: {@data_sources.terrain.diffraction} dB</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="opacity-50">Not available</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-200 rounded-box p-3">
|
||||||
|
<div class="font-semibold mb-1">Surface Observations</div>
|
||||||
|
<div>{length(@surface_observations)} stations</div>
|
||||||
|
<%= if @surface_observations != [] do %>
|
||||||
|
<div>
|
||||||
|
{Calendar.strftime(hd(@surface_observations).observed_at, "%H:%M")} –
|
||||||
|
{Calendar.strftime(List.last(@surface_observations).observed_at, "%H:%M")} UTC
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-200 rounded-box p-3">
|
||||||
|
<div class="font-semibold mb-1">Soundings (RAOB)</div>
|
||||||
|
<div>{length(@soundings)} profiles</div>
|
||||||
|
<%= if @soundings != [] do %>
|
||||||
|
<div>
|
||||||
|
{Enum.map_join(@soundings, ", ", fn s ->
|
||||||
|
"#{s.station.station_code} (#{s.level_count} levels)"
|
||||||
|
end)}
|
||||||
|
</div>
|
||||||
|
<div>{Enum.count(@soundings, & &1.ducting_detected)} with ducting</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-base-200 rounded-box p-3">
|
||||||
|
<div class="font-semibold mb-1">Solar Indices</div>
|
||||||
|
<%= if @solar do %>
|
||||||
|
<div>SFI: {@solar.sfi || "—"}</div>
|
||||||
|
<div>SSN: {@solar.sunspot_number || "—"}</div>
|
||||||
|
<div>Ap: {@solar.ap_index || "—"}</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="opacity-50">Not available</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</Layouts.app>
|
</Layouts.app>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
@ -1081,6 +1161,54 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
defp rad_to_deg(rad), do: rad * 180 / :math.pi()
|
defp rad_to_deg(rad), do: rad * 180 / :math.pi()
|
||||||
defp rem_float(a, b), do: a - Float.floor(a / b) * b
|
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 ──────────────────────────────────────────
|
# ── Propagation analysis ──────────────────────────────────────────
|
||||||
|
|
||||||
defp build_propagation_analysis(contact, hrrr, terrain, elevation_profile, soundings) do
|
defp build_propagation_analysis(contact, hrrr, terrain, elevation_profile, soundings) do
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue