Add Microwaveprop.Backtest: a feature-evaluation framework that runs a (lat, lon, valid_time) -> float function over the historical QSO corpus and a matched random-time baseline, reporting distribution statistics, distance-binned lift, and band-stratified lift. Adds four baseline feature wrappers around the current scorer inputs (naive_gradient, td_depression, time_of_day, pressure), a mix backtest CLI, and the first set of baseline reports under priv/backtest_reports so downstream phases have a frozen reference point to compare against.
62 lines
2 KiB
Elixir
62 lines
2 KiB
Elixir
defmodule Microwaveprop.Backtest.FeaturesTest do
|
|
use Microwaveprop.DataCase, async: true
|
|
|
|
alias Microwaveprop.Backtest.Features
|
|
alias Microwaveprop.Weather
|
|
|
|
@point_lat 32.9
|
|
@point_lon -97.0
|
|
@valid_time ~U[2026-03-28 18:00:00Z]
|
|
|
|
defp create_profile(attrs) do
|
|
base = %{
|
|
valid_time: @valid_time,
|
|
lat: @point_lat,
|
|
lon: @point_lon
|
|
}
|
|
|
|
{:ok, _} = Weather.upsert_hrrr_profile(Map.merge(base, attrs))
|
|
end
|
|
|
|
describe "naive_gradient/3" do
|
|
test "returns the nearest profile's min_refractivity_gradient" do
|
|
create_profile(%{min_refractivity_gradient: -250.0})
|
|
assert Features.naive_gradient(@point_lat, @point_lon, @valid_time) == -250.0
|
|
end
|
|
|
|
test "returns nil when no profile is within match window" do
|
|
assert Features.naive_gradient(@point_lat, @point_lon, @valid_time) == nil
|
|
end
|
|
end
|
|
|
|
describe "td_depression/3" do
|
|
test "returns surface_temp_c minus surface_dewpoint_c" do
|
|
create_profile(%{surface_temp_c: 20.0, surface_dewpoint_c: 14.0})
|
|
assert Features.td_depression(@point_lat, @point_lon, @valid_time) == 6.0
|
|
end
|
|
|
|
test "returns nil when profile has no surface data" do
|
|
create_profile(%{surface_temp_c: nil, surface_dewpoint_c: nil})
|
|
assert Features.td_depression(@point_lat, @point_lon, @valid_time) == nil
|
|
end
|
|
end
|
|
|
|
describe "time_of_day/3" do
|
|
test "returns UTC hour + minute fraction and never calls the database" do
|
|
assert Features.time_of_day(0.0, 0.0, ~U[2026-01-01 06:30:00Z]) == 6.5
|
|
assert Features.time_of_day(0.0, 0.0, ~U[2026-01-01 00:00:00Z]) == 0.0
|
|
assert Features.time_of_day(0.0, 0.0, ~U[2026-01-01 23:45:00Z]) == 23.75
|
|
end
|
|
end
|
|
|
|
describe "pressure/3" do
|
|
test "returns surface_pressure_mb from the nearest profile" do
|
|
create_profile(%{surface_pressure_mb: 1013.2})
|
|
assert Features.pressure(@point_lat, @point_lon, @valid_time) == 1013.2
|
|
end
|
|
|
|
test "returns nil when no profile exists" do
|
|
assert Features.pressure(@point_lat, @point_lon, @valid_time) == nil
|
|
end
|
|
end
|
|
end
|