cover/Elixir.WalletRisk.RiskProfile.html

1 defmodule WalletRisk.RiskProfile do
2 @moduledoc """
3 Aggregated risk profile per user.
4
5 Tracks cumulative risk score, signal history, and review status.
6 Score range: 0..100 (0 = lowest risk, 100 = highest).
7 """
8
9 @type status :: :normal | :flagged | :under_review | :cleared | :blocked
10
11 @type t :: %__MODULE__{
12 profile_id: String.t(),
13 user_id: String.t(),
14 score: non_neg_integer(),
15 status: status(),
16 signal_ids: [String.t()],
17 case_ids: [String.t()],
18 last_scored_at: DateTime.t() | nil,
19 correlation_id: String.t(),
20 metadata: map()
21 }
22
23
:-(
defstruct [
24 :profile_id,
25 :user_id,
26 :score,
27 :status,
28 :signal_ids,
29 :case_ids,
30 :last_scored_at,
31 :correlation_id,
32 metadata: %{}
33 ]
34
35 @spec new(user_id :: String.t(), opts :: keyword()) :: t()
36 5 def new(user_id, opts \\ []) do
37 14 %__MODULE__{
38 profile_id: WalletSharedKernel.Correlation.new_request_id(),
39 user_id: user_id,
40 score: 0,
41 status: :normal,
42 signal_ids: [],
43 case_ids: [],
44 last_scored_at: nil,
45 correlation_id:
46 Keyword.get(opts, :correlation_id, WalletSharedKernel.Correlation.new_correlation_id()),
47 metadata: Keyword.get(opts, :metadata, %{})
48 }
49 end
50
51 @spec apply_score(t(), score :: non_neg_integer()) :: t()
52 def apply_score(profile, score) when score >= 0 and score <= 100 do
53 9 status = score_to_status(score)
54 9 %{profile | score: score, status: status, last_scored_at: DateTime.utc_now()}
55 end
56
57 @spec add_signal(t(), signal_id :: String.t()) :: t()
58 def add_signal(profile, signal_id) do
59 9 %{profile | signal_ids: [signal_id | profile.signal_ids] |> Enum.uniq()}
60 end
61
62 @spec add_case(t(), case_id :: String.t()) :: t()
63 def add_case(profile, case_id) do
64 6 %{profile | case_ids: [case_id | profile.case_ids] |> Enum.uniq()}
65 end
66
67 @spec clear(t()) :: t()
68
:-(
def clear(profile), do: %{profile | status: :cleared}
69
70 3 defp score_to_status(score) when score < 30, do: :normal
71 3 defp score_to_status(score) when score < 60, do: :flagged
72 1 defp score_to_status(score) when score < 85, do: :under_review
73 2 defp score_to_status(_score), do: :blocked
74 end
Line Hits Source