1pub mod datasource;
7pub mod risk;
8
9use risk::{RiskAssessment, RiskEngine};
10
11pub struct ComplianceAnalyzer {
13 risk_engine: RiskEngine,
14}
15
16impl Default for ComplianceAnalyzer {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl ComplianceAnalyzer {
23 pub fn new() -> Self {
25 Self {
26 risk_engine: RiskEngine::new(),
27 }
28 }
29
30 pub async fn analyze_address(
32 &self,
33 address: &str,
34 chain: &str,
35 ) -> anyhow::Result<RiskAssessment> {
36 self.risk_engine.assess_address(address, chain).await
37 }
38
39 pub fn check_sanctions(&self, _address: &str) -> SanctionsCheckResult {
44 SanctionsCheckResult {
46 is_sanctioned: false,
47 lists_checked: vec![],
48 matches: vec![],
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct SanctionsCheckResult {
56 pub is_sanctioned: bool,
57 pub lists_checked: Vec<String>,
58 pub matches: Vec<SanctionsMatch>,
59}
60
61#[derive(Debug, Clone)]
63pub struct SanctionsMatch {
64 pub list_name: String,
65 pub entity_name: String,
66 pub match_type: MatchType,
67 pub confidence: f32,
68}
69
70#[derive(Debug, Clone)]
72pub enum MatchType {
73 Exact,
74 Partial,
75 Associated,
76}
77
78impl SanctionsCheckResult {
79 pub fn has_matches(&self) -> bool {
81 !self.matches.is_empty()
82 }
83
84 pub fn summary(&self) -> String {
86 if self.is_sanctioned {
87 format!(
88 "⚠️ SANCTIONS MATCH FOUND! Checked {} lists, found {} matches.",
89 self.lists_checked.len(),
90 self.matches.len()
91 )
92 } else {
93 format!(
94 "✅ No sanctions matches. Checked: {}",
95 self.lists_checked.join(", ")
96 )
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_sanctions_check_result() {
107 let result = SanctionsCheckResult {
108 is_sanctioned: false,
109 lists_checked: vec!["OFAC".to_string()],
110 matches: vec![],
111 };
112
113 assert!(!result.has_matches());
114 }
115
116 #[test]
117 fn test_sanctions_match_found() {
118 let result = SanctionsCheckResult {
119 is_sanctioned: true,
120 lists_checked: vec!["OFAC".to_string()],
121 matches: vec![SanctionsMatch {
122 list_name: "OFAC".to_string(),
123 entity_name: "Test Entity".to_string(),
124 match_type: MatchType::Exact,
125 confidence: 1.0,
126 }],
127 };
128
129 assert!(result.has_matches());
130 assert!(result.summary().contains("SANCTIONS MATCH"));
131 }
132
133 #[test]
134 fn test_sanctions_no_match_summary() {
135 let result = SanctionsCheckResult {
136 is_sanctioned: false,
137 lists_checked: vec!["OFAC".to_string(), "EU".to_string()],
138 matches: vec![],
139 };
140
141 let summary = result.summary();
142 assert!(summary.contains("No sanctions matches"));
143 assert!(summary.contains("OFAC"));
144 assert!(summary.contains("EU"));
145 }
146
147 #[test]
148 fn test_compliance_analyzer_new() {
149 let analyzer = ComplianceAnalyzer::new();
150 let result = analyzer.check_sanctions("0xtest");
151 assert!(!result.is_sanctioned);
152 assert!(result.lists_checked.is_empty());
153 assert!(result.matches.is_empty());
154 }
155
156 #[test]
157 fn test_compliance_analyzer_default() {
158 let analyzer = ComplianceAnalyzer::default();
159 let result = analyzer.check_sanctions("0xtest");
160 assert!(!result.has_matches());
161 }
162
163 #[tokio::test]
164 async fn test_compliance_analyze_address() {
165 let analyzer = ComplianceAnalyzer::new();
166 let assessment = analyzer
167 .analyze_address("0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2", "ethereum")
168 .await
169 .unwrap();
170 assert_eq!(
171 assessment.address,
172 "0x742d35Cc6634C0532925a3b844Bc9e7595f1b3c2"
173 );
174 assert_eq!(assessment.chain, "ethereum");
175 assert!(!assessment.factors.is_empty());
176 }
177
178 #[test]
179 fn test_match_type_variants() {
180 let exact = MatchType::Exact;
182 let partial = MatchType::Partial;
183 let associated = MatchType::Associated;
184 assert!(format!("{:?}", exact).contains("Exact"));
185 assert!(format!("{:?}", partial).contains("Partial"));
186 assert!(format!("{:?}", associated).contains("Associated"));
187 }
188
189 #[test]
190 fn test_sanctions_check_result_debug() {
191 let result = SanctionsCheckResult {
192 is_sanctioned: false,
193 lists_checked: vec![],
194 matches: vec![],
195 };
196 let debug = format!("{:?}", result);
197 assert!(debug.contains("SanctionsCheckResult"));
198 }
199
200 #[test]
201 fn test_sanctions_match_debug() {
202 let m = SanctionsMatch {
203 list_name: "OFAC".to_string(),
204 entity_name: "Test".to_string(),
205 match_type: MatchType::Partial,
206 confidence: 0.85,
207 };
208 let debug = format!("{:?}", m);
209 assert!(debug.contains("OFAC"));
210 assert!(debug.contains("0.85"));
211 }
212}