1use serde::{Deserialize, Serialize};
4
5#[derive(
7 Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default, PartialOrd, Ord,
8)]
9#[serde(rename_all = "snake_case")]
10pub enum RiskTier {
11 Low,
13 #[default]
15 Medium,
16 High,
18 VeryHigh,
20 Prohibited,
22}
23
24impl RiskTier {
25 pub fn score(&self) -> u8 {
27 match self {
28 Self::Low => 20,
29 Self::Medium => 40,
30 Self::High => 65,
31 Self::VeryHigh => 85,
32 Self::Prohibited => 100,
33 }
34 }
35
36 pub fn requires_enhanced_dd(&self) -> bool {
38 matches!(self, Self::High | Self::VeryHigh)
39 }
40
41 pub fn requires_senior_approval(&self) -> bool {
43 matches!(self, Self::VeryHigh)
44 }
45
46 pub fn review_frequency_months(&self) -> u8 {
48 match self {
49 Self::Low => 36,
50 Self::Medium => 24,
51 Self::High => 12,
52 Self::VeryHigh => 6,
53 Self::Prohibited => 0,
54 }
55 }
56
57 pub fn monitoring_intensity(&self) -> MonitoringIntensity {
59 match self {
60 Self::Low => MonitoringIntensity::Standard,
61 Self::Medium => MonitoringIntensity::Standard,
62 Self::High => MonitoringIntensity::Enhanced,
63 Self::VeryHigh => MonitoringIntensity::Intensive,
64 Self::Prohibited => MonitoringIntensity::Intensive,
65 }
66 }
67
68 pub fn from_score(score: u8) -> Self {
70 match score {
71 0..=30 => Self::Low,
72 31..=50 => Self::Medium,
73 51..=75 => Self::High,
74 76..=95 => Self::VeryHigh,
75 _ => Self::Prohibited,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
82#[serde(rename_all = "snake_case")]
83pub enum MonitoringIntensity {
84 #[default]
86 Standard,
87 Enhanced,
89 Intensive,
91}
92
93impl MonitoringIntensity {
94 pub fn threshold_multiplier(&self) -> f64 {
96 match self {
97 Self::Standard => 1.0,
98 Self::Enhanced => 0.7,
99 Self::Intensive => 0.5,
100 }
101 }
102
103 pub fn requires_manual_review(&self) -> bool {
105 matches!(self, Self::Intensive)
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum SourceOfFunds {
113 Employment,
115 SelfEmployment,
117 Investments,
119 Inheritance,
121 Gift,
123 PropertySale,
125 Pension,
127 GovernmentBenefits,
129 GamblingWinnings,
131 LegalSettlement,
133 Loan,
135 Insurance,
137 CryptoAssets,
139 Other,
141 Unknown,
143}
144
145impl SourceOfFunds {
146 pub fn risk_weight(&self) -> f64 {
148 match self {
149 Self::Employment | Self::Pension | Self::GovernmentBenefits => 0.7,
150 Self::SelfEmployment => 1.2,
151 Self::Investments => 1.0,
152 Self::Inheritance | Self::Gift => 1.3,
153 Self::PropertySale => 1.1,
154 Self::GamblingWinnings => 2.0,
155 Self::LegalSettlement => 1.5,
156 Self::Loan => 1.0,
157 Self::Insurance => 0.9,
158 Self::CryptoAssets => 2.0,
159 Self::Other => 1.5,
160 Self::Unknown => 2.5,
161 }
162 }
163
164 pub fn requires_documentation(&self) -> bool {
166 !matches!(
167 self,
168 Self::Employment | Self::Pension | Self::GovernmentBenefits
169 )
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
175#[serde(rename_all = "snake_case")]
176pub enum SourceOfWealth {
177 CareerEarnings,
179 Inheritance,
181 BusinessOwnership,
183 Investments,
185 RealEstate,
187 BusinessSale,
189 EquityEvent,
191 ProfessionalPractice,
193 Entertainment,
195 CryptoAssets,
197 Other,
199}
200
201impl SourceOfWealth {
202 pub fn risk_weight(&self) -> f64 {
204 match self {
205 Self::CareerEarnings | Self::ProfessionalPractice => 0.8,
206 Self::Inheritance => 1.2,
207 Self::BusinessOwnership | Self::BusinessSale => 1.3,
208 Self::Investments | Self::RealEstate => 1.0,
209 Self::EquityEvent => 1.1,
210 Self::Entertainment => 1.4,
211 Self::CryptoAssets => 2.0,
212 Self::Other => 1.5,
213 }
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct CountryExposure {
220 pub country_code: String,
222 pub exposure_type: CountryExposureType,
224 pub risk_category: CountryRiskCategory,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231pub enum CountryExposureType {
232 Residence,
234 Citizenship,
236 Birth,
238 BusinessOperations,
240 TransactionHistory,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
246#[serde(rename_all = "snake_case")]
247pub enum CountryRiskCategory {
248 Low,
250 #[default]
252 Medium,
253 High,
255 VeryHigh,
257 Sanctioned,
259}
260
261impl CountryRiskCategory {
262 pub fn risk_weight(&self) -> f64 {
264 match self {
265 Self::Low => 0.7,
266 Self::Medium => 1.0,
267 Self::High => 1.5,
268 Self::VeryHigh => 2.5,
269 Self::Sanctioned => 10.0,
270 }
271 }
272
273 pub fn is_prohibited(&self) -> bool {
275 matches!(self, Self::Sanctioned)
276 }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
281#[serde(rename_all = "snake_case")]
282pub enum CashIntensity {
283 VeryLow,
285 #[default]
287 Low,
288 Moderate,
290 High,
292 VeryHigh,
294}
295
296impl CashIntensity {
297 pub fn risk_weight(&self) -> f64 {
299 match self {
300 Self::VeryLow => 0.8,
301 Self::Low => 1.0,
302 Self::Moderate => 1.3,
303 Self::High => 1.8,
304 Self::VeryHigh => 2.5,
305 }
306 }
307
308 pub fn expected_percentage(&self) -> (f64, f64) {
310 match self {
311 Self::VeryLow => (0.0, 0.05),
312 Self::Low => (0.05, 0.15),
313 Self::Moderate => (0.15, 0.30),
314 Self::High => (0.30, 0.50),
315 Self::VeryHigh => (0.50, 1.0),
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_risk_tier_ordering() {
326 assert!(RiskTier::Low < RiskTier::Medium);
327 assert!(RiskTier::Medium < RiskTier::High);
328 assert!(RiskTier::High < RiskTier::VeryHigh);
329 }
330
331 #[test]
332 fn test_risk_tier_from_score() {
333 assert_eq!(RiskTier::from_score(10), RiskTier::Low);
334 assert_eq!(RiskTier::from_score(40), RiskTier::Medium);
335 assert_eq!(RiskTier::from_score(60), RiskTier::High);
336 assert_eq!(RiskTier::from_score(90), RiskTier::VeryHigh);
337 }
338
339 #[test]
340 fn test_source_of_funds_risk() {
341 assert!(
342 SourceOfFunds::CryptoAssets.risk_weight() > SourceOfFunds::Employment.risk_weight()
343 );
344 assert!(SourceOfFunds::Employment.risk_weight() < 1.0);
345 }
346
347 #[test]
348 fn test_country_risk_category() {
349 assert!(CountryRiskCategory::Sanctioned.is_prohibited());
350 assert!(!CountryRiskCategory::High.is_prohibited());
351 }
352
353 #[test]
354 fn test_cash_intensity() {
355 let (min, max) = CashIntensity::High.expected_percentage();
356 assert!(min >= 0.30);
357 assert!(max <= 0.50);
358 }
359}