prop/test/microwaveprop/weather/ncei_metar_client_test.exs
Graham McIntire 3ea2548114 Phase 8: 5-minute METAR ingestion pipeline
NCEI ASOS 5-minute data client (Weather.NceiMetarClient):
- fetch/3 pulls per-station monthly .dat files from NCEI C00418
- parse/1 decodes the fixed-width METAR format including precise
  T-group temperatures (T02110094 → 21.1/9.4°C)
- metar_5min_observations table: schema-identical to
  surface_observations, separate table to avoid mixing cadences

Weather.recent_surface_obs/3 prefers 5-min data when available,
falls back to the hourly surface_observations table.

Data URL: https://www.ncei.noaa.gov/data/automated-surface-observing-system-five-minute/access/YYYY/MM/asos-5min-KXXX-YYYYMM.dat
Available back to 1996.
2026-04-10 08:53:54 -05:00

49 lines
1.9 KiB
Elixir

defmodule Microwaveprop.Weather.NceiMetarClientTest do
use ExUnit.Case, async: true
alias Microwaveprop.Weather.NceiMetarClient
@sample_line "03927KDFW DFW20260301000010303/01/26 00:00:31 5-MIN KDFW 010600Z 17012KT 10SM CLR 21/09 A2997 560 47 1400 160/12 RMK AO2 T02110094"
describe "parse/1" do
test "parses a single KDFW observation" do
[obs] = NceiMetarClient.parse(@sample_line)
assert obs.icao == "KDFW"
assert obs.observed_at == ~U[2026-03-01 00:00:00Z]
# T02110094 → 21.1°C = 70.0°F, 9.4°C = 48.9°F
assert_in_delta obs.temp_f, 70.0, 0.2
assert_in_delta obs.dewpoint_f, 48.9, 0.2
assert obs.wind_speed_kts == 12.0
assert obs.wind_direction_deg == 170
assert_in_delta obs.altimeter_setting, 29.97, 0.01
assert obs.sky_condition == "CLR"
end
test "parses multiple lines" do
text = """
03927KDFW DFW20260301000010303/01/26 00:00:31 5-MIN KDFW 010600Z 17012KT 10SM CLR 21/09 A2997 560 47 1400 160/12 RMK AO2 T02110094
03927KDFW DFW20260301000510303/01/26 00:05:31 5-MIN KDFW 010605Z 17012KT 10SM CLR 21/09 A2997 560 47 1400 160/12 RMK AO2 T02110094
"""
obs = NceiMetarClient.parse(text)
assert length(obs) == 2
assert Enum.at(obs, 0).observed_at == ~U[2026-03-01 00:00:00Z]
assert Enum.at(obs, 1).observed_at == ~U[2026-03-01 00:05:00Z]
end
test "skips blank or too-short lines" do
assert NceiMetarClient.parse("") == []
assert NceiMetarClient.parse("short\n\n") == []
end
test "handles negative temperatures via M prefix" do
line = "03927KDFW DFW20260115120010103/15/26 12:00:31 5-MIN KDFW 151200Z 36005KT 10SM CLR M02/M08 A3032 560 47 1400 360/05 RMK AO2"
[obs] = NceiMetarClient.parse(line)
# M02 = -2°C = 28.4°F, M08 = -8°C = 17.6°F
assert_in_delta obs.temp_f, 28.4, 0.2
assert_in_delta obs.dewpoint_f, 17.6, 0.2
end
end
end