Commit graph

193 commits

Author SHA1 Message Date
c3becdac9f Show beacon submitter callsign, add donate link, prefer 8-char grids
- Add belongs_to :user on Beacon schema, preload in get_beacon!/1
- Show "Submitted by {callsign}" on beacon detail page
- Add NTMS donation link on the about page
- Change grid preference text to 8 characters (e.g. EM12kp37)
2026-04-11 18:08:18 -05:00
1174ecd9e5 Fix all 12 dialyzer warnings
- Replace MapSet with plain list + `in` (features.ex, scorer_diff.ex)
- Remove undefined Beacon.t() type reference (range_estimate.ex)
- Remove dead else branch in find_region (inversion.ex)
- Handle Nx special values in to_float catch-all (recalibrator.ex)
- Remove unreachable catch-all clauses (hrrr_native_client.ex, ncei_metar_client.ex)
- Remove unnecessary nil guards on always-typed values (show.ex)
- Remove dead sky_note/wind_note non-nil clauses (show.ex)
- Remove dead if-guard on always-truthy derive result (hrrr_native_derive.ex)
- Add @spec to path_integrated_conditions (scorer.ex)
2026-04-11 18:08:18 -05:00
d990884cea Force 24-hour time on UTC timestamp inputs 2026-04-11 17:44:39 -05:00
5bcf579009 Add ADIF file upload for contact submission
Parse ADIF tagged-data format with fuzzy frequency-to-band matching
(nearest microwave band for any freq >= 900 MHz). Supports STATION_CALLSIGN,
OPERATOR, CALL, GRIDSQUARE, MY_GRIDSQUARE, FREQ, BAND, QSO_DATE, TIME_ON.
Same dedup and preview/commit flow as CSV upload.

Also fix select dropdown text alignment in daisyUI.
2026-04-11 17:39:05 -05:00
25c703401b Link to existing contact on duplicate submission
Return the conflicting contact from create_contact so the UI can redirect
to it with a flash message instead of just showing a form error.
2026-04-11 17:30:46 -05:00
4ca8c5d3bc Add Q65 contact mode 2026-04-11 17:21:45 -05:00
08ffc7e028 Admin direct edit on contact show page, suggest edit for non-admins 2026-04-11 16:46:46 -05:00
18e920fe54 Fix backfill page missing partitioned table stats, fix contacts map init
Backfill page now sums child partition stats for partitioned tables
like hrrr_profiles (relkind 'p') which were excluded by the relkind
'r' filter. Contacts map defers Leaflet init to requestAnimationFrame
so the flex container has dimensions before map initialization.
2026-04-11 16:34:15 -05:00
77656ba023 Move contacts map controls to sidebar matching main map layout
Replace the JS-built Leaflet control panel with a server-side
sidebar (desktop) and mobile floating controls. Band checkboxes,
callsign filter, and nav links now match the main propagation map
layout. Filter state managed by LiveView, pushed to JS via events.
2026-04-11 16:21:04 -05:00
a66d3094ca Add contact edit approval system with admin review queue
Registered users can suggest edits to any contact's core fields
(callsigns, grids, band, mode, timestamp). Edits enter an admin
approval queue with field-by-field diff view. On approve, changes
are applied and enrichment re-enqueued if grids/band changed.
Users receive email notification on approve or reject.

Also updates dependabot.yml for mix ecosystem.
2026-04-11 16:15:49 -05:00
2aa76727f9 Add callsign filter and band counts to contacts map
- Callsign input filters contacts where either station matches
  (case-insensitive substring match, debounced 300ms)
- Band checkboxes now show per-band count of visible contacts
- All/None buttons for quick band selection
- Header count updates dynamically with filters
- Complete rewrite: rebuilds map on any filter change instead of
  toggling individual layers (simpler, handles cross-filter correctly)
2026-04-11 15:26:57 -05:00
5a1ac63f56 Force dark theme on map sidebar and mobile controls
The sidebar and mobile floating controls overlay the dark map tiles
but inherited light theme colors, making dropdowns, inputs, and
menus invisible (white-on-white). Adding data-theme="dark" forces
all daisyUI components within these containers to use dark variants
regardless of the page's theme setting.
2026-04-11 15:23:26 -05:00
e8ae407be9 Add rain scatter prediction to map detail panel
When clicking a grid point, fetches the latest NEXRAD composite
reflectivity and identifies rain cells within 300 km that could
enable rain scatter contacts. Shows:
- Scatter classification (excellent/good/marginal/none)
- Top 3 cells with dBZ, distance, bearing, and relative signal
- Colored circle markers on the map at rain cell locations
- Markers sized by reflectivity, colored by intensity

Uses simplified bistatic radar equation accounting for reflectivity,
frequency-dependent scattering (Rayleigh/Mie), and R^4 path loss.
NEXRAD cells sampled every ~5 km within bounding box for efficiency.
2026-04-11 15:19:19 -05:00
9fea93f59c Show earth curvature in contact elevation profile
Terrain elevations now include the earth bulge correction, so the
profile visually humps up in the middle relative to the straight
LOS beam — showing how the curved earth rises into the signal path.
Uses actual k-factor from HRRR refractivity when available, otherwise
standard 4/3. For a 300 km path at k=4/3, the midpoint bulge is
~2,650 m (8,700 ft).
2026-04-11 14:55:21 -05:00
8a39ae4411 Improve light mode contrast for score colors and map controls
- Darken tier colors for page elements (path, beacon, contact pages):
  EXCELLENT #00ffa3→#059669, GOOD #7dffd4→#0d9488,
  MARGINAL #ffe566→#ca8a04, POOR #ff9044→#ea580c,
  NEGLIGIBLE #ff4f4f→#dc2626
- Make mobile map controls fully opaque (bg-base-100 not /90)
- Map popups (dark background) keep original vibrant colors
2026-04-11 14:55:21 -05:00
bac0bc3a98 Update about page: ten factors, mention PWAT and native duct detection 2026-04-11 13:52:26 -05:00
b5162daf89 Round lat/lon to 6 decimals everywhere, add commas to EIRP display
- Round grid-derived lat/lon in maybe_fill_latlon changeset step
- Format lat/lon to 6 decimal places on index table
- Migrate existing beacon data to 6 decimal precision
- Add comma separators to EIRP mW display (e.g. 10,000 mW)
- Extract shared add_commas helper for format_freq and format_mw
2026-04-11 09:49:12 -05:00
2c1c221398 Change beacon height_ft from float to integer
Height in feet doesn't need decimal precision. Migrates the DB
column, updates schema type, and strips trailing .0 from form
input so the integer cast succeeds.
2026-04-11 09:44:42 -05:00
0059317043 Beacon page improvements: comma formatting, sorting, rounding
- Add CommaNumber JS hook for live comma formatting while typing
  frequency MHz in the beacon form
- Strip commas server-side before changeset validation
- Order beacon list by most recently added (desc inserted_at)
- Round lat/lon to 6 decimal places in changeset and display
- Round height_ft to integer in changeset and display
- Display coords at 6 decimal places on show page
2026-04-11 09:41:51 -05:00
8425edbdc4 Add comma separators to MHz frequencies on beacon pages
Formats frequencies like 10368.1 as "10,368.1" on both the
beacon list table and detail page (subtitle, map label, stat field).
2026-04-11 09:37:26 -05:00
ac36441102 Remove grid squares toggle from rover planner page
The rover page now shows only: station markers, band selector,
propagation heatmap, and URL sharing. Grid overlay remains on
the main /map page.
2026-04-10 17:31:23 -05:00
ea0c41ef50 Fix crash on /submit when LiveStash restores CSV tab without csv_result assign
Set all assigns before LiveStash recovery so reconnecting to the CSV
tab doesn't crash on missing :csv_result / :csv_preview keys.
2026-04-10 14:04:16 -05:00
a40ea0d62d Show submitter callsign on the contacts index
When a contact is linked to a logged-in user account, display that
user's callsign in the Submitted column instead of a generic "Yes".
Anonymous submissions still show "Yes"; scraped contacts show an em
dash.
2026-04-09 14:02:30 -05:00
f21277cf4f Show upload progress and submission confirmation on /submit
Add a per-entry progress bar (with cancel) while a CSV streams in, and
replace the small post-commit alert with a prominent "X contacts
submitted" confirmation card.
2026-04-09 13:46:54 -05:00
087517252b Rewrite /about in a less-LLM voice
Drops the formal "What we're trying to do / The approach / The stack"
scaffolding for a shorter, first-person telling: line-of-sight is
wrong, here's what we're doing about it, here's what's in the box,
here's what's next. Adds a highlighted note that the scoring is
hand-calibrated and not updated in real time, with links to the
algorithm page and the submit page so readers can help close the
loop.
2026-04-09 13:01:49 -05:00
5724b33e8f Link submissions to the logged-in user on /submit
Contacts now carry a user_id FK to users. When a logged-in user
submits a contact (single or CSV upload), the server injects their
user_id and the email from their account into the submission params,
and the visible email input is replaced with a hidden input holding
that value — logged-in users don't have to retype their address.

The submission changeset now requires at least one of user_id or
submitter_email (rather than hard-requiring email), and email format
is only validated when one is actually present.

Reworded the submit-page blurb from "10 GHz to 241 GHz" to "902 MHz
and up" so it matches the actual band coverage of the project.
2026-04-09 13:01:49 -05:00
90f4e2439d change about 2026-04-09 12:43:30 -05:00
8007346d79 Trim footer and privacy copy
Drop the trailing period on the footer NTMS link, remove the privacy
page's removal-request section, and tidy a stray blank line on About.
2026-04-09 12:30:10 -05:00
44b169b234 CSV upload preview with duplicate detection before commit
Uploads now show a review page with three summary cards (valid,
duplicates, invalid), tables for invalid and duplicate rows, and a
sample of the first 20 valid rows. Nothing is written to the database
until the user clicks "Looks good — submit N contacts".

Duplicate detection treats two rows as the same contact if they share
the same pair of (callsign, grid) tuples (direction-agnostic), the
same band, and timestamps within 60 minutes. Dedup runs against both
existing DB contacts (single band/time-scoped query) and earlier rows
in the same upload, and the preview labels which side matched.

CsvImport grows preview/2 + commit/1; the old import/2 is kept as a
thin backward-compat wrapper that skips dedup. Added 11 new CsvImport
tests and reworked the SubmitLive CSV tests for the two-step flow.
2026-04-09 12:30:10 -05:00
442d1d1bdc Add /privacy page with shared footer across non-map pages
New PrivacyLive page explains that collected/submitted data is used
only for propagation research and will never be sold, traded, or
disclosed. Layouts.app now renders a small footer with the NTMS
attribution and a Privacy link on every page that uses it, so the
full-screen map pages are untouched. The duplicate NTMS credit on
the About page is removed since it now lives in the shared footer.
2026-04-09 09:54:54 -05:00
a95d2ada52 Compact beacon detail page, toggleable coverage, About page formatting
- Beacon map defaults to just the marker at zoom 11. A daisyUI toggle
  switch shows/hides the estimated-coverage cell layer; the coverage
  info line and tier legend are hidden unless the toggle is on.
- Beacon detail list replaced with a compact dl grid (2-4 columns).
  Lat/lon rounded to 5 decimals, power/height/beamwidth formatted via
  Beacon.format_mw/1 so no more "2.5e3" scientific notation or "280.0".
  format_mw/1 hoisted onto the schema so index and show share it.
- About page no longer uses prose classes (this project doesn't load
  @tailwindcss/typography). Headings, lists, and paragraphs now use
  explicit utility classes so they render as intended.
2026-04-09 09:12:32 -05:00
fbae931d7e Add beacon bearing, beamwidth, and weak-signal keyings
Beacons can now record a directional bearing ("omni" or 0-360 degrees)
and a beamwidth in degrees. The keying list grows from {on_off, fsk}
to include FM Voice, WSPR, and the full Q65A/B/C/D/E × 15/30/60/120
family, grouped via optgroups in the submission form.
2026-04-09 08:59:16 -05:00
e327ff584e Map UX polish and populated About page
- /map now centers on the visitor's Cloudflare geolocation when the
  cf-iplatitude/cf-iplongitude headers are present, falling back to DFW.
  Initial HRRR score bounds track the chosen center.
- Added /about link to the /map side navigation.
- Renamed "Propagation Map" label to "Propagation Prediction Map" in
  the /map page title, sidebars, and the /weather back-link.
- Populated /about with mission, approach, stack, roadmap, and a live
  stat grid fed from DB counts (estimates via pg_class.reltuples for
  the multi-million-row tables).
2026-04-08 17:06:53 -05:00
ba0f1161a7 Beacon improvements: anon submit, notes, /map nav, admin backfill
- Anonymous users can submit beacons (held pending admin approval)
- Added notes field to beacons (textarea on form, shown on detail page)
- Added Beacons link to /map sidebar nav (mobile + desktop),
  removed stale Rover Planner link
- Moved /backfill to /admin/backfill under the admin live_session
- Beacon detail map zooms in two extra levels after fitBounds
2026-04-08 16:23:59 -05:00
f5b5af171e Add beacon keying field (on/off or FSK) 2026-04-08 16:13:35 -05:00
80f2725cd5 Beacon submission approval workflow
Non-admin users can now submit beacons but they are held in an
unapproved state until an admin approves them. Only approved beacons
appear in the public list; pending submissions are shown in a separate
admin-only section on /beacons with Approve and Delete actions. The
show page surfaces a pending badge and an admin-only Approve button
when viewing an unapproved beacon.

Also formats EIRP (mW) on the index page without scientific notation.
2026-04-08 15:42:32 -05:00
f8763feb38 Add About page, hide Rover from menu 2026-04-08 15:42:32 -05:00
cda54e3514 Per-HRRR-cell beacon reception plot
Replace the idealised concentric-circle range rings with a realistic
per-HRRR-grid-cell reception map. For every 0.125° grid point within
the band's exceptional range, the estimator computes great-circle
distance, FSPL + atmospheric loss, and applies a cell-specific score
adjustment of (50 - score) * 0.3 dB — score 100 gives a 15 dB ducting
boost, score 0 a 15 dB penalty. Cells below the -145 dBm detection
floor are dropped.

The beacon map hook now renders each surviving cell as a 0.125°
filled rectangle with a tooltip showing tier, Rx dBm, distance, and
HRRR score, so the coverage footprint bulges where ducting conditions
are good and cuts off where they aren't, instead of being a perfect
circle. Falls back to score 50 for cells that don't have HRRR data
yet, so the plot is always useful.

Also fixes the prior all-grey-tiles regression by restoring an
explicit setView() at map creation and adding a post-mount
invalidateSize() for when the map is placed inside a flex container.
2026-04-08 13:43:01 -05:00
67e095bd47 Label beacon TX power as EIRP
The power_mw field represents the beacon's effective radiated power,
not just transmitter output. Relabel it as "TX power (EIRP)" on the
form and detail list, "EIRP (mW)" in the index, and drop the now-
meaningless tx_gain_dbi constant in RangeEstimate since the stored
value already includes antenna gain.
2026-04-08 13:32:32 -05:00
a5b3f1f3da Beacon detail map with range estimate and on_the_air flag
- Add on_the_air boolean to beacons (default true); surfaced as a
  checkbox on the form, a badge on the index, and in the detail list.
- Render a Leaflet map on the beacon show page with a marker at the
  beacon's lat/lon tooltipped with callsign + frequency. Marker is
  green when on air, gray when off.
- Compute and draw a reception-range estimate as concentric signal-
  strength rings. New Microwaveprop.Beacons.RangeEstimate solves a
  link budget (FSPL + O2/H2O absorption from BandConfig) at five RX
  thresholds (-100 to -145 dBm), then scales by 0.5 + score/100 from
  the latest Propagation.point_detail at the beacon's grid square, so
  current HRRR conditions shift the rings in or out.
- Re-enable the hourly PropagationGridWorker cron and freshness
  monitor in dev.exs so dev actually has HRRR-backed scores to feed
  the new estimator.
2026-04-08 13:29:58 -05:00
374f9b106a Beacon height in feet, auto-fill lat/lon from grid
- Rename beacons.height_m to height_ft; migration converts existing
  values (m * 3.28084). Form/list/show labels updated.
- Beacon changeset now fills lat/lon from the grid when they're blank
  (previously only the reverse worked). Lat/lon and grid inputs are no
  longer marked required in the form — enter one side and the other
  populates on blur via phx-change.
2026-04-08 12:55:10 -05:00
b656e015bf Beacon power in mW, settings tweaks
- Rename beacons.power_watts to power_mw (migration multiplies existing
  values by 1000); form/list/show all relabeled to "Power (mW)"
- Fix Add-monitor button alignment on /users/settings by rendering the
  label above and putting the input and button in a plain flex row so
  the input wrapper margin no longer offsets the button
- Extend the settings page re-auth window from 10 minutes to 24 hours
2026-04-08 12:45:35 -05:00
e4fb422402 Dock map controls to right sidebar with collapsible panel
- Move controls (band selector, grid toggle, nav links) to a docked
  right sidebar on desktop, keep floating overlay on mobile
- Point detail panel floats over the map bottom-left instead of inside
  the narrow sidebar
- Add collapse/expand toggle for the sidebar
- Add auth links (login/logout/settings) to sidebar
- Wrap public live routes in live_session with UserAuth on_mount
- Use daisyUI menu lists for sidebar navigation
2026-04-08 12:35:00 -05:00
ee9275e0b9 Add /beacons CRUD and /users admin page
Beacons:
- Scaffolded with phx.gen.live then reworked so reads are public
  and mutations go through a live_session gated by the new
  :require_admin on_mount hook in UserAuth
- Beacon schema stores frequency (MHz), callsign, grid, lat/lon,
  power (W), and height above ground (m); grid auto-derives from
  lat/lon when left blank via Maidenhead.from_latlon
- Adds Maidenhead.from_latlon/3 so we can compute grids locally
  instead of hitting an external API

Users admin page:
- /users and /users/:id/edit (admin-only) for listing, editing
  (callsign/name/email/is_admin), and deleting other users
- Adds Accounts.list_users, admin_update_user, delete_user, and
  a dedicated admin_changeset on the User schema
- Nav gains a "Users" link for admins and a "Beacons" link for
  everyone
2026-04-08 12:01:45 -05:00
27170b5139 Merge auth links into main nav and lower password min to 8
- Register/Log in (or callsign/Settings/Log out) now live next to
  the Map/Path/Rover links in the main header instead of a separate
  top menu bar
- Add on_mount in UserAuth to assign current_scope on LiveView mount,
  and pass current_scope through to Layouts.app from every LiveView
- Drop the old top <ul> from root.html.heex
- Password minimum lowered from 12 to 8 characters
2026-04-08 10:39:51 -05:00
e6aab62ea8 Show grid squares by default on rover planner 2026-04-08 10:21:40 -05:00
27deb02bc7 Add grid squares toggle to rover planner
Extract shared Maidenhead grid overlay into maidenhead_grid.js so both
the main map and rover planner use the same implementation. Wire up the
toggle_grid event and checkbox on the rover page matching the main map.
2026-04-08 09:06:09 -05:00
75fd05a6d1 Simplify rover planner: remove grid toggle and coverage calculation 2026-04-08 09:06:09 -05:00
1bee565d04 Add HRRR propagation overlay to rover planner map 2026-04-07 17:39:03 -05:00
0f885f5c13 Match rover grid toggle to main map's checkbox style 2026-04-07 17:14:11 -05:00