prop/lib/microwaveprop/weather/iemre.ex
Graham McIntire 0c3be97abb
Some checks failed
Build base image / Build and push base image (push) Successful in 3m10s
Build and Push / Build and Push Docker Image (push) Failing after 14s
Build prop-grid-rs / Test, build, push (push) Successful in 12m52s
fix: resolve 27 security, architecture, test, and performance audit findings
P0 (security-critical):
- Gate CSV/ADIF upload tabs behind authentication, add 30s cooldown to all upload handlers
- Cap CSV/ADIF imports at 2,000 rows server-side in both parsers
- Add submitter_verified boolean to contacts (client-cannot-set, anonymous=false)
- Create k8s/secret.example.yaml with placeholders, add LIVE_VIEW_SIGNING_SALT

P1 (high-priority):
- Add Mox.verify_on_exit!() to valkey_test.exs
- Replace DateTime.utc_now() truncation with static ~U literals in map_live_test.exs
- Replace Process.sleep with render_async in pskr_spots_live_test.exs (6 occurrences)
- Add MonitorLive.Show test coverage (4 tests: owner view, non-owner redirect, config success/error)
- Extract duct-detection and mechanism-classification logic from ContactLive.Show into Propagation.PathAnalysis
- Split ContactLive.Show render into 12 function components
- Update CLAUDE.md: remove stale ML model, mark HRDPS active, add backtest/pskr dirs
- Batch CSV import enrichment jobs via new enqueue_for_contacts/1

P2 (medium-priority):
- Set secure:true on session and remember-me cookies in production
- Change SMTP TLS from verify_none to verify_peer with public_key cacerts
- Make /metrics fail-closed in production when PROMETHEUS_AUTH_TOKEN unset
- Add RateLimiter (anon_limit:10, auth_limit:60) to /api/contacts/map
- Add content-security-policy-report-only header
- Add comment noting String.to_atom is compile-time safe in hrdps_client.ex
- Delegate duplicated haversine_km to canonical Microwaveprop.Geo.haversine_km/4
- Consolidate score-tier/color/verdict formatting into Microwaveprop.Format
- Update CLAUDE.md testing section to match actual raw-string-matching practice
- Batch HrrrPointEnqueuer Repo.insert_all calls to single round-trip
- Split weather.ex (1696→216 lines) and radio.ex (1285→54 lines) into purpose-based sub-facades

P3 (low-priority):
- Add LIVE_VIEW_SIGNING_SALT warning comment, extend filter_parameters
- Add host/community validation to snmp_client.ex
- Add raw/1 safety comment in algo_live.ex
- Add hex-audit and cargo-audit Makefile targets
- Add privacy_live smoke test
- Replace notify_listener busy-poll loop with Process.monitor/1 + assert_receive
- Add ContactCommonVolumeRadar changeset validation tests (5 tests)
2026-07-27 18:19:37 -05:00

73 lines
2.3 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

defmodule Microwaveprop.Weather.Iemre do
@moduledoc false
import Ecto.Query
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Repo
alias Microwaveprop.Weather.IemreObservation
alias Microwaveprop.Weather.ProfileLookup
# Delegates to ProfileLookup
defdelegate iemre_for_contact(contact), to: ProfileLookup
defdelegate iemre_for_path(contact), to: ProfileLookup
defdelegate find_nearest_iemre(lat, lon, timestamp), to: ProfileLookup
defdelegate round_to_iemre_grid(lat, lon), to: ProfileLookup
@spec upsert_iemre_observation(map()) :: {:ok, IemreObservation.t()} | {:error, Ecto.Changeset.t()}
def upsert_iemre_observation(attrs) do
%IemreObservation{}
|> IemreObservation.changeset(attrs)
|> Repo.insert(
on_conflict: :nothing,
conflict_target: [:lat, :lon, :date]
)
end
@spec has_iemre_observation?(float(), float(), Date.t()) :: boolean()
def has_iemre_observation?(lat, lon, date) do
IemreObservation
|> where([i], i.lat == ^lat and i.lon == ^lon and i.date == ^date)
|> Repo.exists?()
end
@doc """
Flip `contacts.iemre_status` from `:queued` to `:complete` for every
contact whose path points all have an `iemre_observations` row at
the rounded grid cell for the QSO's UTC date.
Per-contact check (via the existing helpers); kept in Elixir because
`contact_path_points/1` produces 13 points depending on whether
`pos2` is set, and rounding to the 0.125° IEMRE grid per point in
pure SQL is awkward. Queue sizes are small (<20 stuck at steady
state).
"""
@spec reconcile_iemre_statuses() :: {:ok, non_neg_integer()}
def reconcile_iemre_statuses do
queued =
Contact
|> where([c], c.iemre_status == :queued and not is_nil(c.pos1))
|> Repo.all()
to_complete =
Enum.filter(queued, fn c ->
date = DateTime.to_date(c.qso_timestamp)
c
|> Radio.contact_path_points()
|> Enum.all?(fn {lat, lon} ->
{rlat, rlon} = round_to_iemre_grid(lat, lon)
has_iemre_observation?(rlat, rlon, date)
end)
end)
if to_complete == [] do
{:ok, 0}
else
ids = Enum.map(to_complete, & &1.id)
_ = Radio.set_enrichment_status!(ids, :iemre_status, :complete)
{:ok, length(to_complete)}
end
end
end