Commit graph

902 commits

Author SHA1 Message Date
FluxCD
dc7bb5e110 chore: update prop image to git.mcintire.me/graham/prop:main-1776203569-7f1a7fb [skip ci] 2026-04-14 21:53:54 +00:00
7f1a7fb369
Add skew-T log-P diagram to the contact detail page
Render the HRRR pressure-level profile as a full skew-T log-P
diagram on the contact detail page: skewed isotherms, isobars,
dry adiabats, saturation mixing ratio lines, plus the T and Td
traces. Math lives in MicrowavepropWeb.SkewT (Magnus formula,
dry adiabat potential temperature, log-P projection) and is
exercised by a dedicated test module. The atmospheric profile
section now expands by default so the chart is visible without
an extra click.
2026-04-14 16:52:23 -05:00
FluxCD
28b6ca0ac4 chore: update prop image to git.mcintire.me/graham/prop:main-1776203013-e3ddf10 [skip ci] 2026-04-14 21:44:51 +00:00
e3ddf107d9
Gate beacon coverage toggle to 5.76 GHz and above
The estimated current coverage map is only meaningful on bands
where atmospheric attenuation shapes the reach — below 5.76 GHz
it's dominated by free-space loss and misleads. Disable the
toggle, show a helper note explaining the limit, and guard the
handle_event against clients that try to force it.
2026-04-14 16:43:07 -05:00
FluxCD
c454fab83e chore: update prop image to git.mcintire.me/graham/prop:main-1776202569-1d99efb [skip ci] 2026-04-14 21:39:49 +00:00
1d99efb27c
Switch to DynamicLifeline for ~30s orphan rescue on deploys
Replace the timer-based Oban.Plugins.Lifeline (45 min rescue_after)
with Oban.Pro.Plugins.DynamicLifeline, which watches producer
records and rescues orphaned jobs within one rescue_interval (30s)
after the owning pod disappears. This cuts PropagationGridWorker
chain recovery from up to 55 min (wait for next hourly cron) down
to ~30s after a rolling deploy kills a mid-flight step. Bump the
worker's max_attempts 3 → 5 so a couple of rescues during a deploy
don't exhaust the chain's retry budget.
2026-04-14 16:35:42 -05:00
FluxCD
0a8b5db6fe chore: update prop image to git.mcintire.me/graham/prop:main-1776202261-130e062 [skip ci] 2026-04-14 21:32:48 +00:00
130e062054
Persist weather grid for every forecast hour via ProfilesFile
Extend the f00 profile persistence to cover every forecast hour
(f00-f18) so /weather keeps working across pod restarts and shows
forecast-hour atmospheric data without a fresh database. Route the
Weather cold path (latest_grid_valid_time, load_weather_grid,
weather_point_detail) through ProfilesFile first, with the legacy
hrrr_profiles table as a last-resort historical fallback. Warm
GridCache from the latest profile on app start so /weather is hot
the moment a pod boots.
2026-04-14 16:30:40 -05:00
FluxCD
2b4756e921 chore: update prop image to git.mcintire.me/graham/prop:main-1776201355-c9179b3 [skip ci] 2026-04-14 21:17:45 +00:00
c9179b36d5
Merge remote-tracking branch 'github/main' 2026-04-14 16:15:24 -05:00
8af790bd87
Restore point_detail factor breakdown via persisted f00 profiles
The propagation_scores → binary files cutover dropped the factors
JSONB column, which left point_detail returning factors: %{} and
broke the analysis breakdown popup on map clicks. Persist the
enriched f00 grid_data to /data/scores/profiles/{iso}.etf.gz and
rescore on demand at click time so the factor block renders again.
2026-04-14 16:14:55 -05:00
ffcd3d3f60
Restore point_detail factor breakdown via persisted f00 profiles
The propagation_scores → binary files cutover dropped the factors
JSONB column, which left point_detail returning factors: %{} and
broke the analysis breakdown popup on map clicks. Persist the
enriched f00 grid_data to /data/scores/profiles/{iso}.etf.gz and
rescore on demand at click time so the factor block renders again.
2026-04-14 16:14:43 -05:00
FluxCD
b68dcddbc6 chore: update prop image to git.mcintire.me/graham/prop:main-1776198451-ab76d44 [skip ci] 2026-04-14 20:30:35 +00:00
ab76d4449d
Merge remote-tracking branch 'origin/main' 2026-04-14 15:27:04 -05:00
e6952a42c8
Run PropagationGridWorker hourly, retain only the current chain's window
Flip the cron from every-3-hours to every-hour now that a full
f00-f18 chain runs in ~45-60 min instead of ~170 min. With the
:propagation queue's concurrency-of-2, a slow chain won't block
the next one — they interleave, and the file store's last-writer
-wins semantics give the newer run_time's analysis data naturally.

Add ScoresFile.retain_window/2 that deletes any file whose
valid_time falls outside [run_time, run_time + 18h]. Call it at
fh=18 of the chain so leftover files from the previous chain
(specifically the old f00 whose valid_time the new chain doesn't
overwrite because it advanced by one hour) are discarded
immediately instead of waiting for the 2h prune cron.

The step transition path is extracted to handle_step_transition/2
to keep the nesting under credo's limit and make the final-step
logic easier to read.
2026-04-14 15:26:58 -05:00
FluxCD
c12868e0d3 chore: update prop image to git.mcintire.me/graham/prop:main-1776197620-07ff4f8 [skip ci] 2026-04-14 20:17:32 +00:00
07ff4f895e
Merge remote-tracking branch 'origin/main' 2026-04-14 15:13:11 -05:00
811251dd15
Drop propagation_scores table, ScoresFile is the only store
Full cutover: the propagation_scores Postgres table is gone, and
the binary files under /data/scores are the sole source of truth
for the map render path. Three stacked changes:

1. New migration drops the propagation_scores table and its indexes
   (the earlier tuning migrations for it were already applied and
   are now no-ops against a missing table, which is fine — Ecto
   just runs them on fresh environments).

2. Propagation context is gutted of every GridScore reference.
   replace_scores/2 writes files only. upsert_scores/2 is deleted.
   load_scores_from_db, available_valid_times_from_db,
   point_detail_from_db, point_forecast_from_db, fetch_factors,
   coalesce_factors, the Postgres side of prune_old_scores, and
   the Postgres fallbacks in latest/earliest_valid_time are all
   removed. point_detail always returns an empty factors map now
   since factor breakdowns were retired with the table.

3. Deleted modules:
   - Propagation.GridScore (the schema)
   - Propagation.ScorerDiff (read factors from the table)
   - Propagation.AsosNudge (helper for AsosAdjustmentWorker)
   - Workers.AsosAdjustmentWorker (its cron was already disabled)
   - Mix.Tasks.ScorerDiff (wrapper around the deleted module)
   And their tests. AdminTaskWorker's scorer_diff task is a
   logging no-op so any queued Oban rows drain cleanly.
   Release.scorer_diff stays as a stub that tells the operator.

propagation_prune_worker_test rewritten to exercise ScoresFile
pruning. propagation_test.exs rewritten to use replace_scores +
ScoresFile throughout (no more GridScore / upsert_scores paths).
2026-04-14 15:13:01 -05:00
b984794571
Hoist commercial link query + tie pipeline chip to ScoresFile
PropagationGridWorker.merge_commercial_link_data/2 called
Commercial.link_degradation_at twice per grid cell (once to count,
once to merge), and each call re-ran enabled_links plus two sample
queries per in-range link. That was ~500k SQL queries per forecast
hour — fine for the DB but catastrophic for log volume in dev
iex sessions trying to watch a chain step run.

Split into two new functions:
  * build_link_lookup/2 does all the DB work up front — one
    enabled_links query + one link_degradation per enabled link
    (~10 queries total).
  * link_degradation_from_lookup/3 is pure: takes a (lat, lon)
    and the precomputed lookup, returns the aggregate or nil.

The worker now calls build_link_lookup once per forecast hour and
the per-cell path is haversine-only. Net: 500k queries → ~10.

PipelineStatus freshness detection moves off oban_jobs.completed_at
onto ScoresFile.latest_valid_time(). The on-disk files ARE the data
the map renders from, so the chip's "Up to date · Nm ago" and the
2h stale threshold now track actual data presence. Running-state
detection still comes from oban_jobs since the chain step is still
an Oban row. Tests updated to seed a ScoresFile instead of a
completed Oban row for the idle/stale cases.
2026-04-14 15:01:12 -05:00
07ffcf52d7
Flip map read path to ScoresFile, stop writing grid HRRR profiles
Read-side cutover for the binary scores store and a companion
cleanup that removes the biggest remaining DB write from the hot
path.

Propagation.scores_at/3, available_valid_times/1, latest_valid_time/0,
latest_valid_time/1, earliest_valid_time/1, point_detail/4, and
point_forecast/3 all now prefer ScoresFile and fall back to the
propagation_scores table when a file is missing. The map render
path reads from /data/scores first; Postgres stays as a safety
net while dual-write is on. point_detail still pulls factors from
Postgres (analysis-hour rows only) and coalesces nil to an empty
map so the JS popup iterates cleanly.

replace_scores/2 is now gated by a postgres_writes_enabled? flag
(runtime env MICROWAVEPROP_SCORES_POSTGRES=false, or the
:propagation_scores_postgres app env key) so the binary-only path
can be benchmarked locally without the DB insert. Default stays
true.

PropagationGridWorker no longer calls store_hrrr_profiles —
persisting 92k grid rows × 19 forecast hours of JSONB profiles
was ~12 min of wall time per chain for a table only
AsosAdjustmentWorker read from. Per-contact HRRR enrichment
through HrrrFetchWorker still writes its own (is_grid_point:
false) rows. AsosAdjustmentWorker is disabled in all three cron
configs since its data source is gone.

DataCase resets the scores tree between tests so per-test
ScoresFile writes don't leak across cases, and ScoresFileTest
switches to async: false because it mutates the global
:propagation_scores_dir env.
2026-04-14 14:50:43 -05:00
690a3523e2
Dual-write propagation scores to binary files on /data
First step of the disk-backed scores migration from the DuckDB plan
doc. Ended up shipping the raw-binary variant instead of Parquet
because the data is disposable after ~2h — the ecosystem benefits
of Parquet only pay off for long-lived datasets, and the binary
path has zero new dependencies.

Microwaveprop.Propagation.ScoresFile writes one file per
(band_mhz, valid_time) tuple under the configured scores dir,
default /data/scores in prod and priv/dev_scores in dev. Layout is
a 33-byte header plus a dense n_rows × n_cols uint8 array (255 is
the no-data sentinel). The whole CONUS grid serializes to ~93 KB
per band, and writes use the temp-then-rename pattern so NFSv4
concurrent readers never see a partial file.

Propagation.replace_scores/2 now materializes the score stream
once and dual-writes: Postgres on the primary path, then one
ScoresFile per band as a best-effort follow-up (any file error is
logged but doesn't fail the DB write, so we can verify the file
path in prod before cutting readers over).

Propagation.prune_old_scores/0 also clears expired score files so
the existing 15-minute prune cron covers both storage layers.

Dev configuration points at priv/dev_scores/, added to .gitignore.
Test configuration points at a per-run tmp directory.
2026-04-14 14:36:55 -05:00
FluxCD
a354bacd2d chore: update prop image to git.mcintire.me/graham/prop:main-1776194778-6c88c7e [skip ci] 2026-04-14 19:27:22 +00:00
6c88c7ec43
Merge remote-tracking branch 'origin/main' 2026-04-14 14:25:50 -05:00
33a8ba0956
Move NFS share from skippy to node3 at 10.0.15.103:/data
node3's NVMe SSD is the new shared-storage home. Mount the whole
/data export from 10.0.15.103 at /data inside the pod (instead of
the old skippy export of /data/srtm at /srtm) so there's room for
future shared stuff — Parquet score files, maybe a DuckDB database,
or whatever the next thing is — without re-exporting a new path.

SRTM tiles now live at /data/srtm rather than /srtm. Update the
srtm_tiles_dir runtime config to match, and fix the two doc
pointers (CLAUDE.md, duckdb plan) that referenced the old path.

Already applied to the cluster and rolled out; pods confirmed
running with tiles visible at /data/srtm.
2026-04-14 14:25:42 -05:00
FluxCD
4d33fa03e8 chore: update prop image to git.mcintire.me/graham/prop:main-1776193513-79b849e [skip ci] 2026-04-14 19:07:17 +00:00
79b849e617
Merge remote-tracking branch 'origin/main' 2026-04-14 14:04:54 -05:00
c7a1e352a7
Show all executing pipeline workers in the status chip
The :propagation Oban queue runs with concurrency 2, so the grid
worker chain step and the 10-minute ASOS nudge can (and do) execute
in parallel. PipelineStatus.current/0 used to return only the
most-recently-started worker, which made the chip label flip to
"ASOS nudge" at every :00 / :10 tick even though HRRR was still
chugging through forecast hours behind it.

Replace the single :detail field with a list of running_detail
maps, each tagged with :grid or :asos so the chip can:

  - Render one sub-line per currently-executing worker
  - Sort them deterministically (grid before asos) so the eye
    doesn't see rows swap
  - Still override the grid entry with the forecast-hour progress
    label when a propagation_pipeline_progress broadcast arrives
2026-04-14 14:04:44 -05:00
FluxCD
94f7f09512 chore: update prop image to git.mcintire.me/graham/prop:main-1776192495-456e38d [skip ci] 2026-04-14 18:49:13 +00:00
456e38d750
Merge remote-tracking branch 'origin/main' 2026-04-14 13:47:48 -05:00
5ec66df135
Cut propagation_scores write cost with DELETE+COPY, skip-factors, UNLOGGED
The scoring+upsert phase was ~4m40s per forecast hour and dominated
wall time. Three stacked optimizations attack it from different
angles.

replace_scores/2 is a new hot-path writer that does DELETE WHERE
valid_time = $1 followed by a plain insert_all (no ON CONFLICT
resolution). The chain worker rewrites the full (valid_time, all
bands) slice every forecast hour, so conflict detection was pure
waste. AsosAdjustmentWorker still uses upsert_scores because it
only rewrites the subset of cells near a station.

factors is now nullable. Forecast hours f01-f18 pass factors: nil
so the JSONB encode + toast write is skipped entirely — roughly
halves the data volume per run. point_detail/4 coalesces nil to
an empty map so the JS popup renders without a TypeError, and
scorer_diff only pulls the most recent valid_time that still has
factors (the f00 row).

propagation_scores is now UNLOGGED, so inserts bypass WAL entirely.
Durability tradeoff: an unclean shutdown truncates the table, but
PropagationGridWorker rebuilds it from HRRR every 3h so a lost
table is re-populated within one cron cycle.

Also adds docs/plans/2026-04-14-duckdb-scores-storage.md — a
speculative plan for a flat-file / DuckDB rewrite with explicit
trigger conditions for when to pick it up (partitioning deferred
too; revisit only if these three don't solve it).
2026-04-14 13:47:35 -05:00
FluxCD
325f9b769a chore: update prop image to git.mcintire.me/graham/prop:main-1776191148-6bc3e5c [skip ci] 2026-04-14 18:29:07 +00:00
6bc3e5c8d7
Merge remote-tracking branch 'origin/main' 2026-04-14 13:25:17 -05:00
0ed47db8b6
Process one forecast hour per PropagationGridWorker perform
A full f00-f18 sweep takes ~170 min of wall time, longer than the
~90 min gap between deploys on this cluster. Over the last 7 days
every run was killed mid-sweep and zero runs completed. The
propagation_scores table only ever held the scraps from partial
runs that landed before the pod died, which is why the map looked
like it was showing "the current hour" (or nothing).

The worker now processes exactly one forecast hour per perform/1
(~8-10 min) and enqueues the next hour as a fresh Oban job. A cron
fire with empty args seeds the chain at f00; subsequent runs carry
run_time + forecast_hour args. At f18 the chain stops and the
pruner runs. Each step broadcasts propagation:updated immediately
so new hours appear on the map as they land.

Wall time per perform drops from ~170 min to ~10 min, so Lifeline's
2h rescue window is no longer a factor and a pod restart loses at
most one forecast hour instead of the whole sweep. Retries and
max_attempts now describe a single fh, not the whole chain.

Also: bump the :propagation queue from 1 to 2 slots so
PropagationPruneWorker can run alongside the chain job, drop the
worker timeout/1 from 90 min to 20 min to match single-fh runs,
and pull Lifeline rescue_after from 120 min to 45 min to keep the
safety net above the step timeout.

Adds a "Data from HH:MM UTC · Nh ago/now/+Nh" indicator above the
pipeline chip in both the mobile and desktop sidebars so the user
can tell which valid_time the map is showing when the bottom
timeline isn't visible.
2026-04-14 13:24:59 -05:00
FluxCD
9ddb63d99c chore: update prop image to git.mcintire.me/graham/prop:main-1776186441-7e48fbf [skip ci] 2026-04-14 17:08:22 +00:00
7e48fbfc64
Merge remote-tracking branch 'origin/main' 2026-04-14 12:06:52 -05:00
fac604b289
Fix ERA5 cap math + stuck threshold, split chip label onto two lines
Era5SubmitWorker was counting era5_cds_jobs rows 1:1 against the CDS
150-job ceiling, but each row holds TWO CDS job IDs (single-level +
pressure-level). Prod hit 74 rows = 148 in-flight jobs and got stuck
in a reject→resubmit spiral. The cap guard now multiplies row count
by 2 to match what CDS sees.

Era5PollWorker @stuck_after_seconds goes from 4h to 18h. CDS
single-level routinely sits at 'accepted' for 12+h under load while
pressure finishes in ~90 min; the 4h threshold was firing on normal
slow runs and feeding the same spiral.

PipelineStatus now returns `label` + `detail` separately so the map
chip can render "Updating propagation" on one line and the
sub-detail ("HRRR run" / "ASOS nudge" / "+3h" / "now") on a second
line below it. running_label/1 is renamed to running_detail/1 and
returns just the sub-part.
2026-04-14 12:06:39 -05:00
FluxCD
bf1a940ee5 chore: update prop image to git.mcintire.me/graham/prop:main-1776183202-434fca2 [skip ci] 2026-04-14 16:15:09 +00:00
434fca2ad1
Show forecast-hour progress on the map pipeline chip
PropagationGridWorker now broadcasts a {:propagation_pipeline_progress,
%{forecast_hour:, valid_time:}} message on the propagation:pipeline
PubSub topic at the start of each process_forecast_hour/4 call.
MapLive subscribes on mount, stashes the last message in the new
:pipeline_progress assign, and clears it on the refresh tick whenever
PipelineStatus flips out of :running.

The chip component consults a new PipelineStatus.running_label/1
helper that formats the payload as "Updating propagation · now" for
f00 and "Updating propagation · +Nh" for f01-f18, falling back to
the base running label until the first progress message arrives.
Covered by unit tests on the formatter and an integration test in
MapLiveTest that seeds an executing grid job, broadcasts progress,
and asserts the rendered chip.
2026-04-14 11:12:51 -05:00
FluxCD
89b7d39827 chore: update prop image to git.mcintire.me/graham/prop:main-1776182791-7352d06 [skip ci] 2026-04-14 16:09:08 +00:00
7352d06ade
Use NCEPLIBS-g2c v2.1.0 + enable wgrib2 PNG decoder
The linker error from the previous attempt was actual symbol
references, not a flag-combination oddity:

  undefined reference to `g2c_enc_jpeg2000'
  undefined reference to `g2c_dec_jpeg2000'

Those live in g2c's "new" file-based API, built only when
BUILD_G2C=ON. In v1.9.0 the option defaulted OFF and was marked
"experimental until 2.0.0 release", so I had explicitly disabled
it. In v2.1.0 (released 2025-01-13, before wgrib2 v3.6.0 on
2025-02-19) it defaults ON and the API is stable. Bump
G2C_VERSION to 2.1.0 and drop the BUILD_G2C override.

With the new-API symbols now exported, wgrib2 can be compiled
with every decoder we care about in one shot:

  -DUSE_PNG=ON -DUSE_OPENJPEG=ON -DUSE_AEC=ON

`wgrib2 -config` in the final runtime image now reports:

  Supported decoding: simple, complex, rle, ieee, png, jpeg2000,
                      CCSDS AEC

Verified end-to-end by building the full image with podman and
running wgrib2 -config inside it. MrmsFetchWorker's packing
type 41 (PNG) messages will decode once the rolling deploy lands.
2026-04-14 11:06:04 -05:00
4167eb54c9
Switch wgrib2 to OpenJPEG flag to match upstream CI matrix
Last attempt (-DUSE_PNG=ON -DUSE_AEC=ON on wgrib2) reached 100 %
linking libwgrib2.so and then died with "make: Error 2" on the
parallel wgrib2_exe link. wgrib2's own CI workflow
(.github/workflows/Linux_options.yml) never tests USE_PNG alone —
its matrix uses -DUSE_OPENJPEG=ON, and its NCEPLIBS-g2c is built
with -DUSE_OpenJPEG=ON -DUSE_Jasper=OFF. Copying that known-good
combination:

  * g2c now builds with USE_PNG + USE_AEC + USE_OpenJPEG (Jasper
    still disabled — libjasper is out of Debian anyway).
  * wgrib2 flips to -DUSE_OPENJPEG=ON -DUSE_AEC=ON.
  * libopenjp2-7-dev added to the builder apt list, libopenjp2-7
    added to the runtime apt list so libg2c.so + wgrib2 can
    resolve OpenJPEG symbols at exec time.

wgrib2 delegates actual GRIB2 decompression to g2c, which is
built with PNG support, so DRT 5.41 (MRMS PrecipRate) still
decodes through this link — wgrib2's own USE_PNG knob isn't
required for decoding, only for encoding we don't do.
2026-04-14 10:56:42 -05:00
ffe2034869
Build wgrib2 with NCEPLIBS-g2c for PNG + AEC decoder support
The previous attempt to flip `-DUSE_PNG=ON -DUSE_AEC=ON` on wgrib2
broke the image build because wgrib2 v3.6.0's CMakeLists.txt
line 94 forces `find_package(g2c 1.9.0 REQUIRED)` whenever PNG or
JPEG2000 is enabled, and NCEPLIBS-g2c wasn't installed in the
wgrib2-builder stage.

Install NCEPLIBS-g2c v1.9.0 (the version wgrib2 asks for) in the
wgrib2-builder stage ahead of wgrib2 itself, with:

  - USE_PNG=ON           (needs libpng-dev, already present)
  - USE_AEC=ON           (needs libaec-dev, already present)
  - USE_Jasper=OFF       (default ON — would need libjasper-dev
                          and another runtime dep we don't need)
  - USE_OpenJPEG=OFF     (same rationale)
  - BUILD_G2C=OFF        (new experimental API, not needed here)

Then flip wgrib2's PNG/AEC flags back on. wgrib2's binary links
dynamically against libg2c.so so the final stage copies
/usr/local/lib/libg2c.so* from the builder and refreshes the
loader cache with ldconfig.

With the decoder restored, re-add MrmsFetchWorker to the prod
cron in runtime.exs. That worker pulls NOAA MRMS PrecipRate grids
every 2 minutes, which use GRIB2 DRT 5.41 (PNG-compressed) — the
whole reason we needed the PNG decoder in the first place. ASOS
nudges will now have a live radar overlay again.
2026-04-14 10:48:21 -05:00
FluxCD
7c2ea47011 chore: update prop image to git.mcintire.me/graham/prop:main-1776181468-a950fe4 [skip ci] 2026-04-14 15:46:02 +00:00
a950fe4d93
Back out wgrib2 PNG/AEC flags + drop MrmsFetchWorker from prod
The previous commit that added -DUSE_PNG=ON -DUSE_AEC=ON to the
wgrib2 cmake invocation broke the image build: wgrib2 3.6.0 turns
find_package(g2c 1.9.0 REQUIRED) on when those flags are enabled,
and NCEPLIBS-g2c isn't installed in the wgrib2-builder stage.

Revert to the stock wgrib2 cmake config so CI is green again and
pull MrmsFetchWorker out of the prod cron instead. Without the
PNG decoder the worker can't read MRMS PrecipRate (GRIB2 DRT
5.41) and was logging "packing type 41 not supported" every two
minutes. AsosAdjustmentWorker's MRMS path already handles an
empty cache gracefully — the ASOS nudge still runs, it just
drops the live radar overlay.

Re-add MrmsFetchWorker once the Dockerfile installs NCEPLIBS-g2c
and rebuilds wgrib2 with PNG/AEC support.
2026-04-14 10:44:00 -05:00
8672ddbafc
Relocate pipeline status chip to the sidebar footer
The running label "Updating propagation (HRRR run)" didn't fit in
the 224 px desktop sidebar alongside the NTMS title block, so it
was being truncated. Move the chip to the bottom of the sidebar
(below the auth menu) where it can use the full sidebar width,
let the text wrap naturally instead of truncating, and drop the
trailing ellipsis. Mobile chip stays at the top since the mobile
layout collapses menu + auth into a dropdown.
2026-04-14 10:41:11 -05:00
805bbff330
Stop AsosAdjustmentWorker from yanking 120 MB of JSONB per tick
AsosAdjustmentWorker fires every 10 minutes and loads every row of
`hrrr_profiles` on the grid for the latest valid_time. The old query
selected `profile: h.profile` — ~1.3 KB of JSONB × 92k grid points ≈
120 MB of JSONB per tick. Postgrex's Jason.decode! ran inline for
each row and blew past the 15 s pool checkout window, so every tick
was killing connections with:

  DBConnection.ConnectionError: client timed out because it queued
  and checked out the connection for longer than 15000ms

`score_grid_point/4` only touched the profile array to re-derive
`min_refractivity_gradient`, but `hrrr_profiles` already persists
that value as a scalar column at ingestion time. Teach
`derive_from_hrrr/1` to honour the persisted scalar when it's
present and drop `h.profile` from the worker's SELECT list. Net
effect: same score math, ~1% of the JSONB transfer, tick stays
under the pool deadline.

Covered by a new scorer test that feeds a profile map with no
`:profile` list and asserts the refractivity factor still reflects
the persisted gradient instead of the neutral baseline.
2026-04-14 10:37:05 -05:00
d49d9f0cc9
Merge CI image-tag chore 2026-04-14 10:26:43 -05:00
0a1e7ea57e
Build wgrib2 with PNG + AEC decoders
Prod wgrib2 3.6.0 was built without PNG support (cmake had no
-DUSE_PNG flag) so MrmsFetchWorker crashed on every tick with
'packing type 41 not supported' — MRMS PrecipRate tiles are
PNG-compressed (GRIB2 DRT 5.41). libpng-dev was already in the
builder stage; the cmake invocation just never asked wgrib2 to
use it.

Also turn on -DUSE_AEC=ON since libaec-dev is already installed
and some HRRR native-grid variables ship with AEC/CCSDS 121
compression.
2026-04-14 10:26:29 -05:00
FluxCD
800985bb33 chore: update prop image to git.mcintire.me/graham/prop:main-1776179025-a19cb04 [skip ci] 2026-04-14 15:07:46 +00:00
a19cb04298
Merge origin CI chore with pipeline fix + status chip 2026-04-14 10:03:12 -05:00