Commit graph

733 commits

Author SHA1 Message Date
cb6cc8a6e2 Fix map forecast hours, blink, and broken prod email
- Forecast hour clicks showed only a small square of coverage because
  :preload_forecast ran at mount using a hardcoded fallback bounding box
  instead of the client's actual viewport. Move the preload trigger into
  handle_event("map_bounds", ...) so the cache always matches the
  viewport the client just asked about.

- Map overlay blinked on load/update because renderScores called Leaflet
  GridLayer.redraw(), which removes every tile element before recreating
  them. Repaint the existing tile canvases in place instead.

- Prod SMTP to mail.smtp2go.com was failing with an "unexpected_message"
  TLS alert: gen_smtp wasn't sending SNI so the wildcard cert endpoint
  rejected the handshake. Set tls: :always with explicit tls_options
  (server_name_indication, versions, verify).
2026-04-12 13:34:12 -05:00
80de61451c Fix bullet indent on CSV/ADIF upload tabs
list-inside aligned wrapped lines with the bullet marker; use
list-outside + pl-5 so wrapped text indents cleanly past the bullet.
2026-04-12 13:16:06 -05:00
eb1e80958c Normalize ADIF MODE/SUBMODE and document the mapping
- AdifImport.normalize_mode/2 maps ADIF MODE (and SUBMODE when present)
  onto the fixed set of modes the site accepts: CW/SSB/FM/FT8/FT4/Q65.
  SUBMODE takes precedence so `MODE=MFSK, SUBMODE=FT8` correctly imports
  as FT8. USB/LSB/DSB all become SSB; NBFM/WFM become FM.
- Unknown modes (RTTY, PSK31, etc.) map to nil so the row still imports
  — mode is optional on the submission path now.
- AdifImport.mode_mapping_reference/0 returns the user-facing list of
  recognized inputs and their mappings.
- Submit page's ADIF tab renders that list in a table so users can see
  upfront what will happen to their file before uploading.
2026-04-12 13:15:31 -05:00
FluxCD
038f9ad54d chore: update prop image to git.mcintire.me/graham/prop:main-1776017473-76f947d [skip ci] 2026-04-12 18:11:35 +00:00
578b4ede86 Serve /contacts/map payload via HTTP endpoint, parallel hydrate
- Add ContactMapController serving /api/contacts/map with pre-gzipped
  JSON from Microwaveprop.Cache. Browsers handle Content-Encoding: gzip
  natively; payload drops from 5.7 MB raw to 1.5 MB on the wire (73%).
  Cache TTL 10 min, invalidated on contact insert alongside the other
  contact-map cache keys.
- Move contact map payload out of the LiveView's initial HTML entirely.
  Shell renders with the filter chrome (count + band list); the hook
  fetches contacts from the HTTP endpoint in parallel with map init.
  This also avoids Phoenix.Socket's 64 KB frame limit (push_event would
  have required chunked transfer or a global frame size bump).
- Parallelize ContactLive.Show hydrate: five independent DB loads
  (weather, solar, hrrr_path, terrain, iemre) now run concurrently via
  Task.async + Task.await_many. Total latency drops from sum(each) to
  max(each), cutting contact show hydrate time roughly 5x.
2026-04-12 13:10:51 -05:00
FluxCD
01b2a48a93 chore: update prop image to git.mcintire.me/graham/prop:main-1776016575-b686ee9 [skip ci] 2026-04-12 17:57:32 +00:00
253adaf89b Cache /weather grid, defer contact show mount, cache stats
- Add Weather.GridCache: ETS cache of derived HRRR grid rows keyed by
  valid_time, cluster-synced via PubSub. Eagerly warmed from
  PropagationGridWorker after each upsert so /weather map pan/zoom and
  weather_point_detail hit zero DB on warm cache.
- Replace latest_weather_grid DB query path with cache-first lookup +
  DB fallback. hrrr_profiles is 42M rows partitioned; pulling 3-10k
  rows per viewport on every pan was the main cost.
- ContactLive.Show: defer the heavy enrichment loads (weather, solar,
  HRRR, terrain, IEMRE, elevation profile, ITU-R propagation analysis,
  data_sources) into a handle_info(:hydrate) that runs after the shell
  renders. Initial mount now returns nil placeholders; template already
  had :if guards for all of them. Shell-to-first-paint goes from
  ~500ms-2s down to ~20ms.
- Cache fetch_queue_counts for 5s in ContactLive.Show — oban_jobs group
  by query was running on every contact page view.
- Backfill stats: wrap count_unprocessed, fetch_stats, fetch_db_stats
  in Microwaveprop.Cache with 2-5s TTLs; bump refresh debounce from 1s
  to 2s so bulk enrichment events don't thrash the DB.
2026-04-12 12:55:50 -05:00
FluxCD
5b98928fd9 chore: update prop image to git.mcintire.me/graham/prop:main-1776016069-5711118 [skip ci] 2026-04-12 17:48:30 +00:00
6c334d6e18 Cache /contacts, fix map blink, make mode optional
- Add contacts_inserted_at_desc_id_desc_idx so /contacts list runs as an
  Index Scan (~0.4ms) instead of a Seq Scan + top-N heapsort (~77ms on
  58k rows)
- Add Microwaveprop.Cache: tiny ETS-backed TTL cache for memoizing
  expensive-but-stale-tolerant values
- Cache total_entries for unsearched /contacts page loads (30s TTL,
  invalidated on insert)
- Cache the entire /contacts/map payload (pre-encoded JSON + band list),
  moving load_contacts into Radio.contact_map_payload; invalidated on
  new contact insert. Mount goes from ~150-300ms to ~5ms.
- Fix /map overlay blinking on load: reuse the Leaflet GridLayer across
  renderScores calls and just redraw() against the updated ScoreGrid,
  instead of removeLayer + new layer which flashed between tile regens
- Make mode optional on submission: schema change (null: true),
  submission_changeset no longer requires it, blank strings normalise to
  nil, CSV import accepts 6-column (no mode) or 7-column layouts
- core_components .input adds a red asterisk to labels when required,
  giving users a visual cue for which fields must be filled on /submit
2026-04-12 12:47:25 -05:00
FluxCD
8b9d0a29dc chore: update prop image to git.mcintire.me/graham/prop:main-1776014809-7bbd6ad [skip ci] 2026-04-12 17:27:25 +00:00
250709a1b2 Add more caching to make the map feel instant
- ScoreCache stores {band, valid_time} as %{{lat, lon} => score} map so
  point lookups are O(1); adds fetch_point/4 and valid_times/1
- available_valid_times/1 reads directly from ScoreCache when warm,
  falls back to DB on cold start
- point_forecast/3 iterates cached valid_times and uses fetch_point/4
  instead of hitting the DB per click
- NexradCache: node-local ETS cache of decoded n0q PNG pixel buffers
  keyed by 5-minute rounded timestamp; skips ~1-5s HTTP+decode on
  concurrent/repeat clicks within the same window
- MapLive: start_async the rain_scatter fetch so point_detail renders
  immediately with a pending marker; push rain_scatter_update when
  NEXRAD resolves
- MapLive: preload all 18 remaining forecast hours for the current
  viewport after mount/band change/propagation_updated; client caches
  them and renders timeline scrubs instantly without a server roundtrip.
  Adds set_selected_time event for fast-path state sync.
- Propagation map JS: forecastCache map + drawScatterMarkers helper,
  timeline click uses preloaded cache when available
2026-04-12 12:26:25 -05:00
FluxCD
8b407c87ee chore: update prop image to git.mcintire.me/graham/prop:main-1776013912-b322e76 [skip ci] 2026-04-12 17:13:23 +00:00
d880201713 Cache propagation scores in ETS, broadcast across cluster
- Add ScoreCache GenServer with node-local ETS table keyed by
  {band, valid_time}, subscribed to "propagation:cache" PubSub topic so
  every pod stays in sync with a single hourly compute
- scores_at/3 checks cache first, falls back to DB and populates on miss
- PropagationGridWorker warms and broadcasts the cache for each band
  after every forecast hour upsert; prunes >2h old entries
- Replace per-pixel string-keyed Map with flat Int8Array over the CONUS
  grid in propagation_map_hook.ts to eliminate allocations in the tile
  rasterization hot loop (interpolateScore / propagationReach)
2026-04-12 12:11:26 -05:00
FluxCD
6fd0958574 chore: update prop image to git.mcintire.me/graham/prop:main-1776008479-61cb7fb [skip ci] 2026-04-12 15:41:48 +00:00
91fb5cda22 Delay disconnect alerts by 3s to avoid flashing on brief hiccups 2026-04-12 10:40:42 -05:00
FluxCD
e3b2d6315d chore: update prop image to git.mcintire.me/graham/prop:main-1776007906-9cef467 [skip ci] 2026-04-12 15:32:47 +00:00
dd13a5f2a5 Fix three security issues
1. Prevent user_id impersonation in contact submissions
   - Remove user_id from submission_changeset cast fields
   - Pass user_id as a separate server-side argument to create_contact/2
   - Client form params can no longer forge account-linked contacts

2. Restrict flag toggle to admins only
   - Add admin? guard to toggle_flag event handler
   - Hide flag button from non-admin users in template

3. Reset enrichment on timestamp edits
   - Add qso_timestamp to the fields that trigger enrichment reset
   - Prevents stale weather/HRRR/IEMRE data after time changes
2026-04-12 10:31:23 -05:00
FluxCD
361a302736 chore: update prop image to git.mcintire.me/graham/prop:main-1776007679-385cffd [skip ci] 2026-04-12 15:28:45 +00:00
FluxCD
a022321235 chore: update prop image to git.mcintire.me/graham/prop:main-1776007633-515b9fc [skip ci] 2026-04-12 15:27:48 +00:00
163883d0dd Fix donate link contrast on dark mode info alert 2026-04-12 10:27:40 -05:00
7fb340bc35 Fix all remaining credo --strict issues (0 issues)
Aliases: add module aliases for 9 nested module references
Apply: replace apply/3 with direct module attribute calls
Line length: break 1 long spec line
Refactoring: extract helpers to reduce complexity and nesting
in show.ex, radio.ex, weather workers, terrain, duct detection,
backfill dashboard, contact map, and mix tasks
2026-04-12 10:26:53 -05:00
9abbb83469 Fix credo warnings: struct specs, length/1, and test patterns
- Replace %Struct{} with Struct.t() in all @spec annotations
- Replace length(x) > 0 with x != [] in test assertions
- Fix multi-line spec struct references in weather.ex
2026-04-12 10:26:53 -05:00
a07a3c56b4 credo 2026-04-12 10:26:53 -05:00
FluxCD
ae4417d594 chore: update prop image to git.mcintire.me/graham/prop:main-1776004759-c9305f9 [skip ci] 2026-04-12 14:39:31 +00:00
2f7a060f95 Increase test DB query timeout to 60s
The ownership_timeout controls how long a process can hold a checkout,
but the per-query Postgrex timeout (default 15s) is separate. Oban
inline mode chains workers synchronously, so individual queries can
exceed 15s under concurrent test load.
2026-04-12 09:38:59 -05:00
FluxCD
3db8bc85a1 chore: update prop image to git.mcintire.me/graham/prop:main-1776004649-d7acdfe [skip ci] 2026-04-12 14:38:32 +00:00
2769e1a948 Increase test DB ownership timeout to 60s
Prevents sporadic sandbox timeout failures under full concurrent
test load (24 async cases competing for DB connections).
2026-04-12 09:37:08 -05:00
FluxCD
48b365af5e chore: update prop image to git.mcintire.me/graham/prop:main-1776004392-a6efe38 [skip ci] 2026-04-12 14:34:28 +00:00
aba4ca538d Add PayPal donate link to footer and about page 2026-04-12 09:34:01 -05:00
53205d53b4 Remove unused CaptureLog import in elevation client test 2026-04-12 09:32:47 -05:00
e0be0587be Fix backfill stat overflow and select text alignment
- Revert enrichment status cards from daisyUI stat to plain containers
  (tabular status rows don't fit the stat component's layout)
- Add overflow-hidden to DB stat cards for large numbers
- Fix select text centering with display:flex + align-items:center
2026-04-12 09:31:51 -05:00
FluxCD
ccd8aba653 chore: update prop image to git.mcintire.me/graham/prop:main-1776003644-873d5ee [skip ci] 2026-04-12 14:21:09 +00:00
d5d0c32745 Use daisyUI components for stats, alerts, cards, and tabs
- about: stat cards → daisyUI stat, warning/donate boxes → alert
- submit: tab buttons → daisyUI tabs-box, info/instruction boxes → alert
- backfill: enrichment/DB stat boxes → daisyUI stat
- beacon show: stats container → daisyUI card
- contact show: info/status boxes → alert, data source cards → card
- ADIF: strip APP_ prefix from app-defined fields, normalize grid case
2026-04-12 09:20:20 -05:00
FluxCD
2d3da359ee chore: update prop image to git.mcintire.me/graham/prop:main-1776002127-8faae2a [skip ci] 2026-04-12 13:56:04 +00:00
51e390959c Add dialyzer specs and types across the codebase
277 @spec/@type annotations added to 58 files covering all public
APIs: contexts (propagation, radio, weather, terrain, beacons,
commercial), GRIB2 decoders, terrain analysis, duct detection,
rain scatter, CSV/ADIF import, weather clients, and all Ecto
schemas. Dialyzer passes with 0 errors.
2026-04-12 08:55:04 -05:00
FluxCD
7a31d730b4 chore: update prop image to git.mcintire.me/graham/prop:main-1775948922-13b8942 [skip ci] 2026-04-11 23:09:14 +00:00
5f803e4c0d Add 6-char subsquare grid overlay and fix map edge rendering
- Show subsquares (5' x 2.5') at zoom >= 11 with lighter styling
- Pad map bounds by 10% so edge tiles always have score data
- Re-send bounds after 500ms to catch late container layout changes
2026-04-11 18:08:18 -05:00
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
FluxCD
623bb45aee chore: update prop image to git.mcintire.me/graham/prop:main-1775947504-2cf4c21 [skip ci] 2026-04-11 22:46:09 +00:00
d990884cea Force 24-hour time on UTC timestamp inputs 2026-04-11 17:44:39 -05:00
c6bf333c98 Add dialyxir for static analysis
PLT stored in priv/plts/ (gitignored). Run with `mix dialyzer`.
2026-04-11 17:43:18 -05:00
FluxCD
d137975739 chore: update prop image to git.mcintire.me/graham/prop:main-1775947163-2ecd803 [skip ci] 2026-04-11 22:40:08 +00: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
FluxCD
1111e64bdd chore: update prop image to git.mcintire.me/graham/prop:main-1775946667-67df5fc [skip ci] 2026-04-11 22:32:08 +00: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
FluxCD
231f1d9a14 chore: update prop image to git.mcintire.me/graham/prop:main-1775946446-9c64d75 [skip ci] 2026-04-11 22:28:06 +00:00
cdb59be6d7 Deduplicate single contact submissions same as CSV import
Check for existing contacts with the same station pair (order-independent),
band, and grids within a 1-hour window before inserting.
2026-04-11 17:27:03 -05:00
FluxCD
3334db5a08 chore: update prop image to git.mcintire.me/graham/prop:main-1775946134-cd14a06 [skip ci] 2026-04-11 22:23:05 +00:00
4ca8c5d3bc Add Q65 contact mode 2026-04-11 17:21:45 -05:00