Commit graph

267 commits

Author SHA1 Message Date
15ff056a2a Keep source=gps in URL when GPS is used, single copy link, fix unicode
- GPS button sets source_is_gps flag, URL stays source=gps
- Form shows resolved coordinates but URL preserves gps intent
- Single "Copy Link" button copies current URL (includes gps if used)
- Fix unicode escapes in HEEx: use Elixir interpolation for subscripts
2026-04-07 13:56:37 -05:00
1b0b55358b Improve path page UX: prominent GPS button, share links
- "My Location" button with text label (was just an icon)
- Two share buttons after results:
  - "Copy Link" — fixed coordinates, exact result
  - "Copy Link (use viewer's GPS)" — source=gps, dynamic per viewer
- CopyLink JS hook copies full URL to clipboard with "Copied!" feedback
2026-04-07 13:52:43 -05:00
f1e51d679a Support source=gps in URL for dynamic GPS-based bookmarks
Bookmarking /path?source=gps&destination=K5TR&band=10000 will use
the viewer's device GPS to resolve the source location on load.
The LocateMe hook handles both button clicks and server-pushed
request_gps events. Once coords are resolved, the URL is updated
with actual lat,lon and calculation runs automatically.
2026-04-07 13:48:01 -05:00
4ec4cd348b Add forecast timeline, shareable URLs, and real-time form updates to /path
- 18-hour propagation forecast bar chart from grid scores at path midpoint
  Shows best/worst score and time, color-coded bars
- URL query params: all form fields reflected in URL, auto-calculates
  when source+destination present. Bookmarkable/shareable links.
- phx-change on form updates assigns in real-time as user types
- Removed LiveStash in favor of URL-based state persistence
2026-04-07 13:45:57 -05:00
1bd893992b Add station parameters, GPS locate, and detailed budgets to /path
- TX/RX height in feet (converted to meters for terrain analysis)
- TX power in dBm with mW equivalent display
- TX gain and RX gain (dBi) inputs
- Power budget: TX power + TX gain - feed loss = EIRP - path loss
  + RX gain = RX power vs CW sensitivity = margin
- Loss budget: FSPL + gaseous (O2+H2O) + diffraction + rain
  with per-km gas rate detail
- GPS "Locate Me" button using browser geolocation API
- Lat,lon coordinate pairs accepted as input
- Weather displayed in F with C below
- LiveStash preserves form inputs across page reloads
- Terrain cross-section with beam line and Fresnel zone
2026-04-07 13:38:59 -05:00
95351a2358 Add /path calculator page
LiveView page for analyzing microwave propagation between two points.
Features:
- Source/destination input: callsign (via gridmap.org API) or Maidenhead grid
- Band selector for all 8 microwave bands (10-241 GHz)
- Leaflet map showing path line (reuses ContactMap hook)
- Terrain elevation cross-section (reuses ElevationProfile hook)
- Link summary: distance, bearing, terrain verdict
- Loss budget: FSPL + O2 + H2O + rain + diffraction
- Propagation score with 10-factor breakdown
- Current atmospheric conditions from path-integrated HRRR

New CallsignClient for gridmap.org location lookup.
Navigation links added to main navbar and map control panel.
2026-04-07 13:25:04 -05:00
fbf24622fa Fix ERA5 CDS API endpoint for new v1 API
- Submit: /api/retrieve/v1/processes/{dataset}/execution
- Request body wrapped in {"inputs": ...}
- Poll: /api/retrieve/v1/jobs/{jobId}
- Status field: "status" not "state", values: accepted/running/successful/failed
- Download: GET {job_url}/results, follow asset href link
- Add ERA5 credentials to k8s secret manifest
2026-04-07 12:32:21 -05:00
e79e65db43 Add ERA5 to backfill dashboard and enrichment pipeline
- ERA5 checkbox on /backfill page alongside HRRR/weather/terrain/IEMRE
- BackfillEnqueueWorker handles era5 type (targets hrrr_status=unavailable)
- ContactWeatherEnqueueWorker.build_era5_jobs/1 creates ERA5 fetch jobs
  for path points without existing ERA5 profiles
- Progress bar shows ERA5 candidate count (HRRR-unavailable contacts)
- Status panel shows ERA5 candidates and fetched profile count
2026-04-07 12:24:18 -05:00
ddf86ed806 Add mix era5_backfill task and gitignore .envrc
Enqueues ERA5 fetch jobs for contacts where HRRR is unavailable.
Deduplicates by rounded grid point and hour, skips existing profiles.

Usage: mix era5_backfill [limit]
2026-04-07 12:24:18 -05:00
dea407c8ca Add ERA5 reanalysis and RTMA data sources
ERA5 (Copernicus CDS API):
- Era5Profile schema matching HRRR profile structure for interop
- Era5Client with async job submission, polling, GRIB2 download
- Era5FetchWorker (Oban queue: era5, max_attempts: 5)
- Unified lookup: Weather.best_profile_for_contact/1 tries HRRR
  first, falls back to ERA5 for pre-2014 contacts

RTMA (NOAA S3, 2.5km/15-min):
- RtmaObservation schema for surface-only fields
- RtmaClient with byte-range GRIB2 requests (same pattern as HRRR)
- RtmaFetchWorker (Oban queue: rtma, max_attempts: 10)
- Weather.find_nearest_rtma/3 for surface condition lookup

Both queues added to production Oban config (2 workers each).
2026-04-07 12:04:16 -05:00
7652e41971 Path-integrated HRRR scoring across pos1/mid/pos2
Contact scoring now uses all HRRR profiles along the path instead of
just pos1. Aggregation strategy:
- Best along path for beneficial factors (refractivity, pressure)
- Worst along path for harmful factors (rain, wind)
- Average for neutral factors (temp, dewpoint, PWAT, BL depth)

Scorer.path_integrated_conditions/2 merges multiple profiles into a
single conditions map. Falls back gracefully to single-profile scoring
when only one profile is available.
2026-04-07 12:04:16 -05:00
2eefe64c9b Update seasonal scores from sounding data, document commercial link patterns
Soundings (10,935): October has strongest gradients (-350 N/km) and
65.7% ducting, raised seasonal score 82→88. February ducting (20.2%)
exceeds January (14.8%), raised 32→40.

Commercial links (12,068 samples): 68 GHz shows 3.9 dB diurnal swing,
11 GHz shows 1.7 dB with inverted multipath pattern, 24 GHz remarkably
stable at 0.9 dB. Diurnal sensitivity is non-monotonic with frequency.

RAOB gradients avg -265 N/km vs HRRR -107 (2.5x stronger) — confirms
HRRR misses thin surface ducts that soundings resolve.
2026-04-07 11:45:44 -05:00
3cdd132f24 Data-driven algorithm refinements from full dataset analysis
Analyzed 37,925 HRRR-matched contacts from 58,367 total.

- Remove shallow BL bonus (score 82 for HPBL < 300m): data shows
  medium BL (1000-2000m) produces longest contacts (222 km avg),
  not shallow (210 km). Refractivity fallback now uses default score.

- Refine pressure scoring bins: add <980 mb tier (score 88), steeper
  gradient from low to high. Contacts at <970 mb avg 242.7 km vs
  184.1 km at 990-1000 mb.

- Update algo.md calibration stats (58,367 contacts, 41M HRRR
  profiles, 58,361 terrain profiles) and document dataset bias
  (99.5% Aug-Sep).

- Create updates.md with full binned analysis tables for all factors.
2026-04-07 11:15:49 -05:00
548e277bec Add bin/notebook script to start Phoenix + Livebook together
Starts Phoenix with a named node and Livebook attached to it on
port 8081. Single command, Ctrl-C stops both.
2026-04-07 11:15:49 -05:00
44f0ff26e5 Add mix notebook task for Livebook attached to dev node
Run `mix notebook` for instructions to start Livebook at localhost:8081
connected to the running Phoenix node with full Repo/Scorer access.
2026-04-07 11:15:49 -05:00
976cd1a4c5 Remove icons from navigation links in map control panels 2026-04-07 07:50:36 -05:00
a774e98350 Smooth viewshed boundary to remove SRTM elevation spikes
Apply 5-point circular moving average to reach_km values before
rendering the boundary polygon. Eliminates sharp spikes caused by
isolated terrain anomalies in SRTM data.
2026-04-07 07:42:32 -05:00
721d59ed77 Remove stale propagation scores maintenance banner 2026-04-07 07:38:03 -05:00
e03b9fbc0e Add periodic enrichment backfill cron and terrain reconciliation
Runs BackfillEnqueueWorker every 30 minutes to pick up contacts with
pending/queued/failed enrichment status and enqueue missing jobs.
Before enqueueing, reconciles terrain contacts stuck in "queued" by
checking the terrain_profiles FK directly.
2026-04-07 07:36:57 -05:00
79130a39e8 Mark terrain status complete when profile already exists
TerrainProfileWorker early-exited with :ok when a profile existed
but never updated the contact status, leaving contacts stuck at queued.
2026-04-06 16:54:29 -05:00
646467cf4d Store stub records on empty weather fetches to prevent infinite backfill retries
When ASOS/RAOB/IEMRE APIs return empty data, store a stub record so
dedup checks (has_surface_observations?, has_sounding?, has_iemre_observation?)
see coverage and don't recreate the same jobs on future backfill runs.
2026-04-06 16:10:39 -05:00
c3819112fe Persist checkbox and limit state across backfill enqueue submissions 2026-04-06 12:46:08 -05:00
98289ec16e Fix format_number reversing digits
Enum.reverse/1 was reversing each chunk's characters, scrambling the
displayed numbers (e.g. 58,363 displayed as 85,363).
2026-04-06 12:01:25 -05:00
9ffeba3fc9 Suppress health check request logs
/health gets hit constantly by k8s probes, drowning out useful logs.
2026-04-06 11:55:01 -05:00
4ba7269e57 Prioritize pending contacts over queued in backfill
Queued contacts just need reconciliation (check if data exists),
while pending contacts need actual HRRR fetches. Process pending
first so real work happens before reconciliation.
2026-04-06 11:48:55 -05:00
e99e585b66 Add type filter to backfill enqueue
Backfill dashboard now has checkboxes to select which enrichment types
to process (HRRR, Weather, Terrain, IEMRE). Prevents weather jobs from
flooding the queue when only HRRR needs work.
2026-04-06 11:42:26 -05:00
0e885001bc Widen HRRR profile spatial matching from 0.05° to 0.07°
The propagation grid stores profiles at 0.125° intervals, so contacts
up to 0.0625° from a grid point would miss with the old 0.05° tolerance.
This caused has_hrrr_profile? and find_nearest_hrrr to fail matching
against existing data, leaving contacts incorrectly stuck as pending.
2026-04-06 10:40:25 -05:00
eeb92bcc19 Fix HRRR enrichment status stuck at queued/pending
enqueue_for_contact unconditionally marked contacts as :queued even
when no HRRR jobs were needed (profiles already exist). Workers never
updated contact status to :complete after storing profiles. Contacts
got stuck and were never re-processed.

- Mark :complete when no jobs generated (data already exists)
- Include :queued in backfill enrichable states for reconciliation
2026-04-06 10:24:56 -05:00
091d469c6a Enable OS and Ecto data in LiveDashboard, fix progress bars
- Add ecto_psql_extras for PostgreSQL insights in dashboard
- Add os_mon for OS metrics (CPU, memory, disk)
- Fix progress bars using percentage instead of raw large numbers
2026-04-06 09:56:08 -05:00
2de94e2318 Add Erlang clustering via libcluster and expose LiveDashboard
- libcluster with Kubernetes IP strategy for pod discovery
- RBAC for pod list/get, POD_IP from downward API, shared cookie
- LiveDashboard at /dashboard for all environments
2026-04-06 09:46:48 -05:00
5bee273442
Combine active jobs and completed cards on backfill dashboard 2026-04-06 09:03:44 -05:00
bb32e12c6a
Fix backfill progress layout - wider card, no text wrapping 2026-04-06 09:02:32 -05:00
95d7fc0c39
Show per-type enrichment progress bars on backfill dashboard 2026-04-06 08:59:38 -05:00
f4d3773e2b
Fix backfill progress to use exact counts instead of pg_stat estimates 2026-04-06 08:54:30 -05:00
0b68cb2d29
Disable propagation grid worker and add maintenance banner to map 2026-04-05 17:08:40 -05:00
8d008ba934
Add logging to WeatherFetchWorker for ASOS and RAOB fetches 2026-04-05 10:26:10 -05:00
7fd8f3165c
Add deterministic tiebreaker to contacts sort order 2026-04-05 09:27:34 -05:00
d1b1b4cc7e
Show flag emoji on contacts list for flagged_invalid contacts 2026-04-05 09:15:02 -05:00
16883591b4
Database performance fixes and async backfill enqueue
- Fix score_pressure crash on nil pressure_mb (coastal HRRR points)
- Set 10-min timeout on grid score upsert transaction (was :infinity)
- Single DELETE for prune_old_scores instead of N queries in a loop
- Remove dead load_hrrr_refractivity that loaded 95k rows into nil map
- Pass selected_time to point_detail to skip latest_valid_time sub-query
- Batch station existence checks (1 query per path point, not per station)
- Batch solar index upserts via insert_all in chunks of 500
- Batch backfill_distances via single UPDATE FROM VALUES statement
- Add is_grid_point boolean + partial index to hrrr_profiles (replaces
  non-sargable modular arithmetic filter on every weather map query)
- Add partial index on contacts(qso_timestamp) WHERE pos1 IS NOT NULL
- Move backfill enqueue to Oban worker so UI returns immediately
2026-04-04 19:19:18 -05:00
a209fc65ae
Restrict enrichment enqueue to T-Mobile CGNAT subnet (172.56.0.0/13)
Store remote_ip in session, check subnet in LiveView mount before
enqueuing weather/HRRR/terrain/solar enrichment jobs.
2026-04-04 18:55:42 -05:00
dc66252fdc
Reduce dead tuples by using conditional upserts and skip-on-conflict
- HRRR, IEMRE, terrain: on_conflict: :nothing (immutable data)
- Surface obs, soundings, solar: conditional WHERE (only update when values differ)
- set_enrichment_status!: skip rows already at target status
2026-04-04 17:32:57 -05:00
383c18e50a
Show IEMRE gridded reanalysis data on contact detail page 2026-04-04 11:35:30 -05:00
da490f7c59
Add tooltip to enrichment badge showing per-type status 2026-04-04 11:26:35 -05:00
6d2f1fb4c3
Fix swapped Submitted/Enriched column headers 2026-04-04 11:22:06 -05:00
72b272cf5c
Add enrichment status badge column to contacts table 2026-04-04 11:15:18 -05:00
915e8bdccf
Add UTC labels to Timestamp and Added column headers 2026-04-04 11:11:01 -05:00
95a2afaa01
Add inserted_at column to contacts table 2026-04-04 10:51:39 -05:00
e107706915
Show dead tuple counts on backfill dashboard, fix table visibility during vacuum
Removed n_live_tup > 0 filter that hid tables when pg_stats reports 0 rows
during VACUUM. Added dead tuple column with warning highlight to monitor
vacuum progress. Also includes Styler reformatting and unused dep cleanup.
2026-04-04 10:45:06 -05:00
23aa2786ab
Skip updating unchanged scores to reduce dead tuples, tune autovacuum
ON CONFLICT now only replaces score/factors when the score actually changed,
avoiding dead tuple generation for the ~80% of grid points that don't change
between consecutive HRRR runs.

Migration sets aggressive autovacuum on propagation_scores: zero cost delay,
2000 cost limit, 1% scale factor. The table was at 72GB with 108M dead rows
because default autovacuum couldn't keep pace with 14M upserts per hour.
2026-04-04 09:53:45 -05:00
4759c65809
Support two-callsign search on contacts page to find contacts between a pair 2026-04-04 09:29:32 -05:00