cover/Elixir.WalletRisk.RiskCase.html

1 defmodule WalletRisk.RiskCase do
2 @moduledoc """
3 Risk escalation case created when a RiskProfile crosses review/block threshold.
4
5 States:
6 open -> under_investigation -> resolved | escalated_to_compliance
7 escalated_to_compliance is terminal.
8 resolved is terminal.
9 """
10
11 @type status :: :open | :under_investigation | :resolved | :escalated_to_compliance
12
13 @type t :: %__MODULE__{
14 case_id: String.t(),
15 user_id: String.t(),
16 profile_id: String.t(),
17 score_at_creation: non_neg_integer(),
18 status: status(),
19 investigator_id: String.t() | nil,
20 resolution_note: String.t() | nil,
21 escalation_reason: String.t() | nil,
22 correlation_id: String.t(),
23 opened_at: DateTime.t(),
24 resolved_at: DateTime.t() | nil,
25 metadata: map()
26 }
27
28
:-(
defstruct [
29 :case_id,
30 :user_id,
31 :profile_id,
32 :score_at_creation,
33 :status,
34 :investigator_id,
35 :resolution_note,
36 :escalation_reason,
37 :correlation_id,
38 :opened_at,
39 :resolved_at,
40 metadata: %{}
41 ]
42
43 @terminal_statuses [:resolved, :escalated_to_compliance]
44
45 @spec new(user_id :: String.t(), profile_id :: String.t(), score :: non_neg_integer(), opts :: keyword()) :: t()
46 6 def new(user_id, profile_id, score, opts \\ []) do
47 12 %__MODULE__{
48 case_id: WalletSharedKernel.Correlation.new_request_id(),
49 user_id: user_id,
50 profile_id: profile_id,
51 score_at_creation: score,
52 status: :open,
53 investigator_id: nil,
54 resolution_note: nil,
55 escalation_reason: nil,
56 correlation_id:
57 Keyword.get(opts, :correlation_id, WalletSharedKernel.Correlation.new_correlation_id()),
58 opened_at: DateTime.utc_now(),
59 resolved_at: nil,
60 metadata: Keyword.get(opts, :metadata, %{})
61 }
62 end
63
64 @spec assign(t(), investigator_id :: String.t()) :: {:ok, t()} | {:error, :terminal_state}
65 1 def assign(%__MODULE__{status: s}, _) when s in @terminal_statuses,
66 do: {:error, :terminal_state}
67
68 3 def assign(risk_case, investigator_id) do
69 {:ok, %{risk_case | status: :under_investigation, investigator_id: investigator_id}}
70 end
71
72 @spec resolve(t(), resolution_note :: String.t()) :: {:ok, t()} | {:error, :terminal_state}
73 1 def resolve(%__MODULE__{status: s}, _) when s in @terminal_statuses,
74 do: {:error, :terminal_state}
75
76 2 def resolve(risk_case, resolution_note) do
77 {:ok, %{risk_case | status: :resolved, resolution_note: resolution_note,
78 resolved_at: DateTime.utc_now()}}
79 end
80
81 @spec escalate_to_compliance(t(), reason :: String.t()) ::
82 {:ok, t()} | {:error, :terminal_state}
83
:-(
def escalate_to_compliance(%__MODULE__{status: s}, _) when s in @terminal_statuses,
84 do: {:error, :terminal_state}
85
86 3 def escalate_to_compliance(risk_case, reason) do
87 {:ok, %{risk_case | status: :escalated_to_compliance, escalation_reason: reason,
88 resolved_at: DateTime.utc_now()}}
89 end
90
91 @spec terminal?(t()) :: boolean()
92 2 def terminal?(%__MODULE__{status: s}), do: s in @terminal_statuses
93 end
Line Hits Source