refactor(propagation): drop ShadowComparator (Phase 1 only)
Phase 2 cutover completed in 65693ed — Rust prop-grid-rs owns
f01-f18 production writes and the shadow path has been dead
for weeks. No callers in lib/, test/, or mix tasks.
This commit is contained in:
parent
9ced0eab87
commit
a81e55e348
1 changed files with 0 additions and 119 deletions
|
|
@ -1,119 +0,0 @@
|
|||
defmodule Microwaveprop.Propagation.ShadowComparator do
|
||||
@moduledoc """
|
||||
Shadow-mode validator for the Rust `prop-grid-rs` port.
|
||||
|
||||
During Phase 1 shadow mode the Rust worker writes score-grid files to
|
||||
`/data/scores_shadow/<band>/<iso>.ntms` while Elixir keeps writing to
|
||||
`/data/scores/`. This module compares the two files and logs the
|
||||
per-band delta distribution so drift is visible before cutover.
|
||||
|
||||
Callable from IEx on demand or from a one-shot Mix task; not started
|
||||
in the supervision tree (this is temporary instrumentation).
|
||||
"""
|
||||
|
||||
alias Microwaveprop.Propagation.ScoresFile
|
||||
|
||||
require Logger
|
||||
|
||||
@default_prod_dir "/data/scores"
|
||||
@default_shadow_dir "/data/scores_shadow"
|
||||
|
||||
@type diff :: %{
|
||||
band_mhz: non_neg_integer(),
|
||||
valid_time: DateTime.t(),
|
||||
total_cells: non_neg_integer(),
|
||||
matching_cells: non_neg_integer(),
|
||||
missing_shadow_cells: non_neg_integer(),
|
||||
max_abs_delta: non_neg_integer(),
|
||||
mean_abs_delta: float()
|
||||
}
|
||||
|
||||
@spec compare(non_neg_integer(), DateTime.t(), keyword()) ::
|
||||
{:ok, diff()} | {:error, term()}
|
||||
def compare(band_mhz, %DateTime{} = valid_time, opts \\ []) do
|
||||
prod_dir = Keyword.get(opts, :prod_dir, @default_prod_dir)
|
||||
shadow_dir = Keyword.get(opts, :shadow_dir, @default_shadow_dir)
|
||||
|
||||
with {:ok, prod} <- read_file(prod_dir, band_mhz, valid_time),
|
||||
{:ok, shadow} <- read_file(shadow_dir, band_mhz, valid_time) do
|
||||
{:ok, diff_payloads(band_mhz, valid_time, prod, shadow)}
|
||||
end
|
||||
end
|
||||
|
||||
defp read_file(dir, band_mhz, valid_time) do
|
||||
path = band_mhz |> ScoresFile.path_for(valid_time) |> swap_base(dir)
|
||||
|
||||
case File.read(path) do
|
||||
{:ok, binary} -> decode_scores_file(binary)
|
||||
{:error, reason} -> {:error, {:read, path, reason}}
|
||||
end
|
||||
end
|
||||
|
||||
defp swap_base(path, new_base) do
|
||||
suffix = Path.relative_to(path, ScoresFile.base_dir())
|
||||
Path.join(new_base, suffix)
|
||||
end
|
||||
|
||||
# Inline parse of the v1 scores-file on-disk format so this module
|
||||
# doesn't reach into `ScoresFile`'s private `decode/1`. The format is
|
||||
# documented in `ScoresFile`'s moduledoc; only the body size and
|
||||
# dimensions matter for a diff, so we intentionally keep this minimal.
|
||||
# The literal "NTMS" magic is the file-header marker already on disk.
|
||||
defp decode_scores_file(
|
||||
<<"NTMS", 1::unsigned-8, _band_mhz::unsigned-little-32, _valid_time_unix::signed-little-64,
|
||||
_lat_min::float-little-32, _lon_min::float-little-32, _step::float-little-32, n_rows::unsigned-little-16,
|
||||
n_cols::unsigned-little-16, body::binary>>
|
||||
) do
|
||||
expected = n_rows * n_cols
|
||||
|
||||
if byte_size(body) == expected do
|
||||
{:ok, %{n_rows: n_rows, n_cols: n_cols, scores: body}}
|
||||
else
|
||||
{:error, :invalid_format}
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_scores_file(_), do: {:error, :invalid_format}
|
||||
|
||||
defp diff_payloads(band_mhz, valid_time, prod, shadow) do
|
||||
%{scores: p_body, n_cols: n_cols, n_rows: n_rows} = prod
|
||||
%{scores: s_body} = shadow
|
||||
|
||||
{total, matching, missing, max_abs, sum_abs} =
|
||||
for row <- 0..(n_rows - 1),
|
||||
col <- 0..(n_cols - 1),
|
||||
reduce: {0, 0, 0, 0, 0} do
|
||||
{t, m, miss, max_d, sum_d} ->
|
||||
idx = row * n_cols + col
|
||||
p = :binary.at(p_body, idx)
|
||||
s = :binary.at(s_body, idx)
|
||||
|
||||
cond do
|
||||
p == 255 and s == 255 ->
|
||||
{t, m, miss, max_d, sum_d}
|
||||
|
||||
p != 255 and s == 255 ->
|
||||
{t + 1, m, miss + 1, max_d, sum_d}
|
||||
|
||||
p == s ->
|
||||
{t + 1, m + 1, miss, max_d, sum_d}
|
||||
|
||||
true ->
|
||||
d = abs(p - s)
|
||||
{t + 1, m, miss, max(max_d, d), sum_d + d}
|
||||
end
|
||||
end
|
||||
|
||||
mean = if total > 0, do: sum_abs / total, else: 0.0
|
||||
|
||||
%{
|
||||
band_mhz: band_mhz,
|
||||
valid_time: valid_time,
|
||||
total_cells: total,
|
||||
matching_cells: matching,
|
||||
missing_shadow_cells: missing,
|
||||
max_abs_delta: max_abs,
|
||||
mean_abs_delta: mean
|
||||
}
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue