cover/Elixir.WalletRisk.RiskSignal.html

1 defmodule WalletRisk.RiskSignal do
2 @moduledoc """
3 Risk signal emitted from transfer, auth, or integration events.
4
5 A signal captures a single observable risk indicator for a user/transfer.
6 Signals are accumulated into a RiskProfile via the scoring engine.
7 """
8
9 @type signal_type ::
10 :high_value_transfer
11 | :unusual_velocity
12 | :geo_anomaly
13 | :device_change
14 | :failed_auth_spike
15 | :pattern_match
16 | :manual_flag
17
18 @type t :: %__MODULE__{
19 signal_id: String.t(),
20 user_id: String.t(),
21 transfer_id: String.t() | nil,
22 signal_type: signal_type(),
23 severity: :low | :medium | :high | :critical,
24 score_contribution: non_neg_integer(),
25 description: String.t(),
26 source: :rule_engine | :manual | :ml_model | :integration,
27 correlation_id: String.t(),
28 occurred_at: DateTime.t(),
29 metadata: map()
30 }
31
32
:-(
defstruct [
33 :signal_id,
34 :user_id,
35 :transfer_id,
36 :signal_type,
37 :severity,
38 :score_contribution,
39 :description,
40 :source,
41 :correlation_id,
42 :occurred_at,
43 metadata: %{}
44 ]
45
46 @spec new(user_id :: String.t(), signal_type :: signal_type(), opts :: keyword()) :: t()
47
:-(
def new(user_id, signal_type, opts \\ []) do
48 20 %__MODULE__{
49 signal_id: WalletSharedKernel.Correlation.new_request_id(),
50 user_id: user_id,
51 transfer_id: Keyword.get(opts, :transfer_id),
52 signal_type: signal_type,
53 severity: Keyword.get(opts, :severity, :medium),
54 score_contribution: Keyword.get(opts, :score_contribution, default_contribution(signal_type)),
55 20 description: Keyword.get(opts, :description, "Risk signal: #{signal_type}"),
56 source: Keyword.get(opts, :source, :rule_engine),
57 correlation_id:
58 Keyword.get(opts, :correlation_id, WalletSharedKernel.Correlation.new_correlation_id()),
59 occurred_at: DateTime.utc_now(),
60 metadata: Keyword.get(opts, :metadata, %{})
61 }
62 end
63
64 3 defp default_contribution(:high_value_transfer), do: 20
65 4 defp default_contribution(:unusual_velocity), do: 25
66 2 defp default_contribution(:geo_anomaly), do: 15
67 1 defp default_contribution(:device_change), do: 10
68
:-(
defp default_contribution(:failed_auth_spike), do: 30
69 3 defp default_contribution(:pattern_match), do: 35
70 7 defp default_contribution(:manual_flag), do: 40
71
:-(
defp default_contribution(_), do: 10
72 end
Line Hits Source