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)
|
||||
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
|
|||
</table>
|
||||
</div>
|
||||
<% 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>
|
||||
"""
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue