prop/lib/microwaveprop/radio.ex
Graham McIntire 6f16395f44
Add QSO import, solar indices, Oban workers, LiveView UI, and parallel weather import
- Radio context with QSO schema and CSV import script (58K contacts)
- Solar index schema, GFZ client, and daily Oban cron worker
- LiveView dashboard with QSO table and weather correlation views
- Parallelize weather import script using Task.async_stream (10 concurrent workers)
- Replace sequential rate_limit sleeps with concurrency-based backpressure
- Atomic progress counter for interleave-safe reporting
2026-03-29 13:04:55 -05:00

49 lines
1.1 KiB
Elixir

defmodule Microwaveprop.Radio do
@moduledoc false
import Ecto.Query
alias Microwaveprop.Radio.Qso
alias Microwaveprop.Repo
@per_page 20
@sortable_fields ~w(station1 station2 band mode distance_km qso_timestamp)a
def list_qsos(opts \\ []) do
page = max(Keyword.get(opts, :page, 1), 1)
offset = (page - 1) * @per_page
{sort_field, sort_dir} = sort_opts(opts)
total_entries = Repo.aggregate(Qso, :count)
total_pages = max(ceil(total_entries / @per_page), 1)
entries =
Qso
|> order_by([q], [{^sort_dir, field(q, ^sort_field)}])
|> limit(^@per_page)
|> offset(^offset)
|> Repo.all()
%{
entries: entries,
page: page,
total_pages: total_pages,
total_entries: total_entries
}
end
defp sort_opts(opts) do
sort_by = Keyword.get(opts, :sort_by, :qso_timestamp)
sort_order = Keyword.get(opts, :sort_order, :desc)
field = if sort_by in @sortable_fields, do: sort_by, else: :qso_timestamp
dir = if sort_order in [:asc, :desc], do: sort_order, else: :desc
{field, dir}
end
def get_qso!(id) do
Repo.get!(Qso, id)
end
end