Skip to main content

datasynth_core/models/banking/
risk_tier.rs

1//! Risk tier definitions for KYC/AML.
2
3use serde::{Deserialize, Serialize};
4
5/// Customer risk tier for KYC purposes.
6#[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 risk - simplified due diligence allowed
12    Low,
13    /// Medium risk - standard due diligence
14    #[default]
15    Medium,
16    /// High risk - enhanced due diligence required
17    High,
18    /// Very high risk - senior management approval required
19    VeryHigh,
20    /// Prohibited - relationship not allowed
21    Prohibited,
22}
23
24impl RiskTier {
25    /// Numeric score for calculations (0-100).
26    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    /// Whether enhanced due diligence is required.
37    pub fn requires_enhanced_dd(&self) -> bool {
38        matches!(self, Self::High | Self::VeryHigh)
39    }
40
41    /// Whether senior management approval is required.
42    pub fn requires_senior_approval(&self) -> bool {
43        matches!(self, Self::VeryHigh)
44    }
45
46    /// Review frequency in months.
47    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    /// Transaction monitoring intensity.
58    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    /// Create from a numeric score (0-100).
69    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/// Transaction monitoring intensity level.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
82#[serde(rename_all = "snake_case")]
83pub enum MonitoringIntensity {
84    /// Standard automated monitoring
85    #[default]
86    Standard,
87    /// Enhanced monitoring with lower thresholds
88    Enhanced,
89    /// Intensive monitoring with manual review
90    Intensive,
91}
92
93impl MonitoringIntensity {
94    /// Alert threshold multiplier (1.0 = standard).
95    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    /// Whether manual review is required for alerts.
104    pub fn requires_manual_review(&self) -> bool {
105        matches!(self, Self::Intensive)
106    }
107}
108
109/// Source of funds classification.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum SourceOfFunds {
113    /// Regular employment income
114    Employment,
115    /// Self-employment / business income
116    SelfEmployment,
117    /// Investment returns
118    Investments,
119    /// Inheritance
120    Inheritance,
121    /// Gift
122    Gift,
123    /// Sale of property
124    PropertySale,
125    /// Pension / retirement
126    Pension,
127    /// Government benefits
128    GovernmentBenefits,
129    /// Lottery / gambling winnings
130    GamblingWinnings,
131    /// Legal settlement
132    LegalSettlement,
133    /// Loan proceeds
134    Loan,
135    /// Insurance payout
136    Insurance,
137    /// Crypto / digital assets
138    CryptoAssets,
139    /// Other
140    Other,
141    /// Unknown / undeclared
142    Unknown,
143}
144
145impl SourceOfFunds {
146    /// Risk weight (1.0 = standard).
147    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    /// Whether documentation is typically required.
165    pub fn requires_documentation(&self) -> bool {
166        !matches!(
167            self,
168            Self::Employment | Self::Pension | Self::GovernmentBenefits
169        )
170    }
171}
172
173/// Source of wealth classification (for HNW customers).
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
175#[serde(rename_all = "snake_case")]
176pub enum SourceOfWealth {
177    /// Built through employment/career
178    CareerEarnings,
179    /// Inherited wealth
180    Inheritance,
181    /// Business ownership
182    BusinessOwnership,
183    /// Investment appreciation
184    Investments,
185    /// Real estate appreciation
186    RealEstate,
187    /// Sale of business
188    BusinessSale,
189    /// IPO / equity event
190    EquityEvent,
191    /// Professional practice (doctor, lawyer)
192    ProfessionalPractice,
193    /// Entertainment / sports
194    Entertainment,
195    /// Crypto / digital assets
196    CryptoAssets,
197    /// Other
198    Other,
199}
200
201impl SourceOfWealth {
202    /// Risk weight (1.0 = standard).
203    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/// Country risk classification for geographic exposure.
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct CountryExposure {
220    /// Country code (ISO 3166-1 alpha-2)
221    pub country_code: String,
222    /// Exposure type
223    pub exposure_type: CountryExposureType,
224    /// Risk category
225    pub risk_category: CountryRiskCategory,
226}
227
228/// Type of country exposure.
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231pub enum CountryExposureType {
232    /// Country of residence
233    Residence,
234    /// Country of citizenship
235    Citizenship,
236    /// Country of birth
237    Birth,
238    /// Business operations in country
239    BusinessOperations,
240    /// Regular transactions with country
241    TransactionHistory,
242}
243
244/// Country risk category.
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
246#[serde(rename_all = "snake_case")]
247pub enum CountryRiskCategory {
248    /// Low risk (FATF compliant, low corruption)
249    Low,
250    /// Medium risk
251    #[default]
252    Medium,
253    /// High risk (weak AML framework)
254    High,
255    /// Very high risk (FATF grey list)
256    VeryHigh,
257    /// Sanctioned / prohibited (FATF black list, OFAC)
258    Sanctioned,
259}
260
261impl CountryRiskCategory {
262    /// Risk weight (1.0 = standard).
263    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    /// Whether transactions should be blocked.
274    pub fn is_prohibited(&self) -> bool {
275        matches!(self, Self::Sanctioned)
276    }
277}
278
279/// Cash intensity classification.
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
281#[serde(rename_all = "snake_case")]
282pub enum CashIntensity {
283    /// Very low cash usage (<5%)
284    VeryLow,
285    /// Low cash usage (5-15%)
286    #[default]
287    Low,
288    /// Moderate cash usage (15-30%)
289    Moderate,
290    /// High cash usage (30-50%)
291    High,
292    /// Very high cash usage (>50%)
293    VeryHigh,
294}
295
296impl CashIntensity {
297    /// Risk weight (1.0 = standard).
298    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    /// Expected cash transaction percentage.
309    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}