prop/lib/microwaveprop/geocoder.ex
Graham McIntire 4e6c87eca2
feat(telemetry): broaden Instrument span coverage
Adds spans to 15 previously-unmeasured hot paths so every question we
might ask while tuning has a histogram to answer it:

External I/O:
- iem.fetch_iemre (gridded weather reanalysis)
- mrms.list_latest / mrms.download (precip radar)
- rtma.fetch_observation
- ncei.fetch_metar (historical 5-min METAR backfill)
- solar.fetch_indices (GFZ solar indices)
- swpc.fetch (SWPC Kp/F10.7/X-ray)
- giro.fetch (ionosonde)
- qrz.request, geocoder.geocode (callsign enrichment)
- srtm.download_tile (terrain tile download + gunzip)
- hrrr.download_grib_ranges (parallel byte-range fetch phase)

Subprocess:
- wgrib2.extract_grid / extract_grid_from_file / extract_grid_from_file_mapped

LiveView hot paths:
- propagation.scores_at (map score fetch + cache hit/miss counter)
- propagation.point_forecast (sparkline)
- propagation.point_detail (click-to-inspect)
- propagation.daily_outlook_at (/map outlook strip)

Worker-level end-to-end:
- worker.terrain_profile
- worker.mechanism_classify
- worker.mrms_fetch

Each event is registered in Microwaveprop.PromEx.InstrumentPlugin as
a Prometheus histogram (default / long buckets as appropriate) plus
a counter for the scores_at cache hit/miss ratio. Prometheus at
10.0.15.25 will start seeing the new series on the next scrape after
deploy.
2026-04-18 17:25:33 -05:00

45 lines
1.4 KiB
Elixir

defmodule Microwaveprop.Geocoder do
@moduledoc """
Thin wrapper around the Google Maps Geocoding API. Only used as a
fallback when QRZ's own `<lat>` / `<lon>` fields are missing on a
callsign record.
"""
@spec geocode(String.t()) :: {:ok, %{lat: float(), lon: float()}} | {:error, String.t()}
def geocode(address) do
Microwaveprop.Instrument.span([:geocoder, :geocode], %{}, fn ->
case Req.get(client(),
url: "/maps/api/geocode/json",
params: [address: address, key: api_key()]
) do
{:ok, %{status: 200, body: %{"status" => "OK", "results" => [first | _]}}} ->
%{"geometry" => %{"location" => %{"lat" => lat, "lng" => lon}}} = first
{:ok, %{lat: lat, lon: lon}}
{:ok, %{status: 200, body: %{"status" => "ZERO_RESULTS"}}} ->
{:error, "No results found"}
{:ok, %{status: 200, body: %{"status" => status}}} ->
{:error, "Geocoding failed: #{status}"}
{:error, reason} ->
{:error, "Geocoding request failed: #{inspect(reason)}"}
end
end)
end
defp client do
config = Application.get_env(:microwaveprop, __MODULE__, [])
[base_url: "https://maps.googleapis.com"]
|> Req.new()
|> Req.Request.register_options([:plug, :api_key])
|> Req.merge(config)
end
defp api_key do
:microwaveprop
|> Application.get_env(__MODULE__, [])
|> Keyword.get(:api_key, "")
end
end