defmodule Microwaveprop.Weather.Grib2.SimplePackingTest do use ExUnit.Case, async: true alias Microwaveprop.Weather.Grib2.SimplePacking describe "extract_value/3" do test "basic 16-bit unpacking" do # value = (R + X * 2^E) * 10^(-D) # R=0.0, E=0, D=0, N=16 → value = X params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 16, num_data_points: 4 } # 4 values: 100, 200, 300, 400 as 16-bit big-endian data = <<100::16-big, 200::16-big, 300::16-big, 400::16-big>> assert {:ok, 100.0} = SimplePacking.extract_value(params, data, 0) assert {:ok, 200.0} = SimplePacking.extract_value(params, data, 1) assert {:ok, 300.0} = SimplePacking.extract_value(params, data, 2) assert {:ok, 400.0} = SimplePacking.extract_value(params, data, 3) end test "applies reference value" do # R=273.15, E=0, D=0 → value = 273.15 + X params = %{ reference_value: 273.15, binary_scale: 0, decimal_scale: 0, bits_per_value: 16, num_data_points: 2 } data = <<10::16-big, 20::16-big>> assert {:ok, val} = SimplePacking.extract_value(params, data, 0) assert_in_delta val, 283.15, 0.001 end test "applies binary scale factor" do # R=0, E=2, D=0 → value = X * 2^2 = X * 4 params = %{ reference_value: 0.0, binary_scale: 2, decimal_scale: 0, bits_per_value: 16, num_data_points: 1 } data = <<25::16-big>> assert {:ok, val} = SimplePacking.extract_value(params, data, 0) assert_in_delta val, 100.0, 0.001 end test "applies decimal scale factor" do # R=0, E=0, D=2 → value = X * 10^(-2) = X / 100 params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 2, bits_per_value: 16, num_data_points: 1 } data = <<15_000::16-big>> assert {:ok, val} = SimplePacking.extract_value(params, data, 0) assert_in_delta val, 150.0, 0.001 end test "combined R, E, D" do # value = (R + X * 2^E) * 10^(-D) # R=100.0, E=1, D=1 → (100 + X * 2) / 10 params = %{ reference_value: 100.0, binary_scale: 1, decimal_scale: 1, bits_per_value: 16, num_data_points: 1 } data = <<50::16-big>> assert {:ok, val} = SimplePacking.extract_value(params, data, 0) # (100 + 50 * 2) / 10 = 200 / 10 = 20.0 assert_in_delta val, 20.0, 0.001 end test "12-bit non-byte-aligned extraction" do # 3 values of 12 bits each = 36 bits = 4.5 bytes # Values: 0xABC=2748, 0x123=291, 0xDEF=3567 params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 12, num_data_points: 3 } # Pack 3 12-bit values: 0xABC, 0x123, 0xDEF # Binary: 1010_1011_1100 | 0001_0010_0011 | 1101_1110_1111 # As bytes: AB C1 23 DE F0 (with 4 bits padding) data = <<0xAB, 0xC1, 0x23, 0xDE, 0xF0>> assert {:ok, val0} = SimplePacking.extract_value(params, data, 0) assert_in_delta val0, 2748.0, 0.001 assert {:ok, val1} = SimplePacking.extract_value(params, data, 1) assert_in_delta val1, 291.0, 0.001 assert {:ok, val2} = SimplePacking.extract_value(params, data, 2) assert_in_delta val2, 3567.0, 0.001 end test "zero bits_per_value returns reference value" do params = %{ reference_value: 293.5, binary_scale: 0, decimal_scale: 0, bits_per_value: 0, num_data_points: 10 } # With 0 bits, data is empty data = <<>> assert {:ok, val} = SimplePacking.extract_value(params, data, 0) assert_in_delta val, 293.5, 0.001 assert {:ok, val} = SimplePacking.extract_value(params, data, 5) assert_in_delta val, 293.5, 0.001 end test "negative binary scale factor" do # E=-1 → 2^(-1) = 0.5 params = %{ reference_value: 0.0, binary_scale: -1, decimal_scale: 0, bits_per_value: 16, num_data_points: 1 } data = <<200::16-big>> assert {:ok, val} = SimplePacking.extract_value(params, data, 0) assert_in_delta val, 100.0, 0.001 end test "index out of range returns error" do params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 16, num_data_points: 2 } data = <<100::16-big, 200::16-big>> assert {:error, :index_out_of_range} = SimplePacking.extract_value(params, data, 5) end end describe "extract_values/3" do test "extracts multiple values in a single batch call" do params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 16, num_data_points: 4 } data = <<100::16-big, 200::16-big, 300::16-big, 400::16-big>> assert {:ok, result} = SimplePacking.extract_values(params, data, [0, 2, 3]) assert map_size(result) == 3 assert_in_delta result[0], 100.0, 0.001 assert_in_delta result[2], 300.0, 0.001 assert_in_delta result[3], 400.0, 0.001 end test "skips out-of-range indices" do params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 16, num_data_points: 2 } data = <<100::16-big, 200::16-big>> assert {:ok, result} = SimplePacking.extract_values(params, data, [0, 5, -1, 1]) assert map_size(result) == 2 assert_in_delta result[0], 100.0, 0.001 assert_in_delta result[1], 200.0, 0.001 end test "returns all same value when bits_per_value is 0" do params = %{ reference_value: 293.5, binary_scale: 0, decimal_scale: 0, bits_per_value: 0, num_data_points: 10 } assert {:ok, result} = SimplePacking.extract_values(params, <<>>, [0, 3, 7]) assert map_size(result) == 3 assert_in_delta result[0], 293.5, 0.001 assert_in_delta result[3], 293.5, 0.001 assert_in_delta result[7], 293.5, 0.001 end test "applies scaling correctly to batch" do # value = (R + X * 2^E) * 10^(-D) # R=100.0, E=1, D=1 -> (100 + X*2) / 10 params = %{ reference_value: 100.0, binary_scale: 1, decimal_scale: 1, bits_per_value: 16, num_data_points: 2 } data = <<50::16-big, 75::16-big>> assert {:ok, result} = SimplePacking.extract_values(params, data, [0, 1]) # (100 + 50*2)/10 = 20.0 assert_in_delta result[0], 20.0, 0.001 # (100 + 75*2)/10 = 25.0 assert_in_delta result[1], 25.0, 0.001 end test "handles empty indices list" do params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 16, num_data_points: 2 } data = <<100::16-big, 200::16-big>> assert {:ok, result} = SimplePacking.extract_values(params, data, []) assert result == %{} end test "handles non-byte-aligned 12-bit batch extraction" do params = %{ reference_value: 0.0, binary_scale: 0, decimal_scale: 0, bits_per_value: 12, num_data_points: 3 } # Pack 3 12-bit values: 0xABC=2748, 0x123=291, 0xDEF=3567 data = <<0xAB, 0xC1, 0x23, 0xDE, 0xF0>> assert {:ok, result} = SimplePacking.extract_values(params, data, [0, 1, 2]) assert_in_delta result[0], 2748.0, 0.001 assert_in_delta result[1], 291.0, 0.001 assert_in_delta result[2], 3567.0, 0.001 end end end