defmodule CodeNameRaven.Channel.Notification do @moduledoc """ The payload delivered to a channel when an alert fires. Produced by the Handler when a monitor's status changes to a condition that matches a configured alert rule. Passed to `Channel.deliver/2`. Never crosses node or version boundaries — created and consumed within a single handler process — so it carries no `vsn`/upcast machinery. """ @type status :: :up | :degraded | :down | :unknown @type t :: %__MODULE__{ monitor_id: String.t(), monitor_name: String.t() | nil, status: status(), previous_status: status() | nil, occurred_at: DateTime.t(), node: node() | nil, instance_id: String.t() | nil, alert_started_at: DateTime.t() | nil } defstruct [ :monitor_id, :monitor_name, :status, :previous_status, :occurred_at, :node, :instance_id, :alert_started_at ] @doc """ Seconds between `alert_started_at` and `occurred_at`, or nil when the alert's start is unknown. Shared by channel formatters so "was down N minutes" is computed one way everywhere. """ @spec duration_down(t()) :: non_neg_integer() | nil def duration_down(%__MODULE__{alert_started_at: nil}), do: nil def duration_down(%__MODULE__{occurred_at: nil}), do: nil def duration_down(%__MODULE__{alert_started_at: started, occurred_at: occurred}) do max(DateTime.diff(occurred, started, :second), 0) end @doc """ Human-readable duration-down ("14m", "3h 12m", "2d 5h"), or nil when the alert's start is unknown. Sub-minute durations render as "<1m". """ @spec format_duration_down(t()) :: String.t() | nil def format_duration_down(%__MODULE__{} = n) do case duration_down(n) do nil -> nil s when s < 60 -> "<1m" s when s < 3600 -> "#{div(s, 60)}m" s when s < 86_400 -> "#{div(s, 3600)}h #{rem(s, 3600) |> div(60)}m" s -> "#{div(s, 86_400)}d #{rem(s, 86_400) |> div(3600)}h" end end end