datasynth_generators/compliance/
regulation_generator.rs1use chrono::NaiveDate;
7use serde::Serialize;
8
9use datasynth_standards::registry::StandardRegistry;
10
11#[derive(Debug, Clone, Serialize)]
13pub struct ComplianceStandardRecord {
14 pub standard_id: String,
15 pub body: String,
16 pub number: String,
17 pub title: String,
18 pub category: String,
19 pub domain: String,
20 pub jurisdiction: String,
21 pub effective_date: String,
22 pub version: String,
23 pub is_active: bool,
24 pub superseded_by: Option<String>,
25 pub applicable_account_types: Vec<String>,
27 pub applicable_processes: Vec<String>,
29}
30
31#[derive(Debug, Clone, Serialize)]
33pub struct CrossReferenceRecord {
34 pub from_standard: String,
35 pub to_standard: String,
36 pub relationship: String,
37 pub convergence_level: f64,
38 pub description: Option<String>,
39}
40
41#[derive(Debug, Clone, Serialize)]
43pub struct JurisdictionRecord {
44 pub country_code: String,
45 pub country_name: String,
46 pub accounting_framework: String,
47 pub audit_framework: String,
48 pub standards_body: String,
49 pub statutory_tax_rate: f64,
50 pub standard_count: usize,
51}
52
53pub struct RegulationGenerator {
55 registry: StandardRegistry,
56}
57
58impl RegulationGenerator {
59 pub fn new() -> Self {
61 Self {
62 registry: StandardRegistry::with_built_in(),
63 }
64 }
65
66 pub fn with_registry(registry: StandardRegistry) -> Self {
68 Self { registry }
69 }
70
71 pub fn registry(&self) -> &StandardRegistry {
73 &self.registry
74 }
75
76 pub fn generate_standard_records(
78 &self,
79 jurisdictions: &[String],
80 reference_date: NaiveDate,
81 ) -> Vec<ComplianceStandardRecord> {
82 let mut records = Vec::new();
83
84 for jurisdiction in jurisdictions {
85 let standards = self
86 .registry
87 .standards_for_jurisdiction(jurisdiction, reference_date);
88
89 for std in standards {
90 let active_version =
91 self.registry
92 .active_version_in(&std.id, jurisdiction, reference_date);
93
94 let (effective_date, version) = if let Some(v) = active_version {
95 (v.effective_from.to_string(), v.version_id.clone())
96 } else {
97 ("unknown".to_string(), "unknown".to_string())
98 };
99
100 records.push(ComplianceStandardRecord {
101 standard_id: std.id.as_str().to_string(),
102 body: std.id.body().to_string(),
103 number: std.id.number().to_string(),
104 title: std.title.clone(),
105 category: format!("{}", std.category),
106 domain: format!("{}", std.domain),
107 jurisdiction: jurisdiction.clone(),
108 effective_date,
109 version,
110 is_active: true,
111 superseded_by: std.superseded_by.as_ref().map(|s| s.as_str().to_string()),
112 applicable_account_types: std.applicable_account_types.clone(),
113 applicable_processes: std.applicable_processes.clone(),
114 });
115 }
116 }
117
118 records
119 }
120
121 pub fn generate_cross_reference_records(&self) -> Vec<CrossReferenceRecord> {
123 self.registry
124 .cross_references()
125 .iter()
126 .map(|xr| CrossReferenceRecord {
127 from_standard: xr.from_standard.as_str().to_string(),
128 to_standard: xr.to_standard.as_str().to_string(),
129 relationship: format!("{}", xr.relationship),
130 convergence_level: xr.convergence_level,
131 description: xr.description.clone(),
132 })
133 .collect()
134 }
135
136 pub fn generate_jurisdiction_records(
138 &self,
139 jurisdictions: &[String],
140 reference_date: NaiveDate,
141 ) -> Vec<JurisdictionRecord> {
142 jurisdictions
143 .iter()
144 .filter_map(|code| {
145 self.registry.jurisdiction(code).map(|jp| {
146 let standard_count = self
147 .registry
148 .standards_for_jurisdiction(code, reference_date)
149 .len();
150
151 JurisdictionRecord {
152 country_code: jp.country_code.clone(),
153 country_name: jp.country_name.clone(),
154 accounting_framework: format!("{:?}", jp.accounting_framework),
155 audit_framework: format!("{:?}", jp.audit_framework),
156 standards_body: jp.accounting_standards_body.clone(),
157 statutory_tax_rate: jp.corporate_tax_rate.unwrap_or(0.0),
158 standard_count,
159 }
160 })
161 })
162 .collect()
163 }
164}
165
166impl Default for RegulationGenerator {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172#[cfg(test)]
173#[allow(clippy::unwrap_used)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_generate_standard_records() {
179 let gen = RegulationGenerator::new();
180 let date = NaiveDate::from_ymd_opt(2025, 6, 30).unwrap();
181 let records = gen.generate_standard_records(&["US".to_string()], date);
182 assert!(!records.is_empty(), "Should have US standards");
183 }
184
185 #[test]
186 fn test_generate_cross_references() {
187 let gen = RegulationGenerator::new();
188 let records = gen.generate_cross_reference_records();
189 assert!(!records.is_empty(), "Should have cross-references");
190 }
191
192 #[test]
193 fn test_generate_jurisdiction_records() {
194 let gen = RegulationGenerator::new();
195 let date = NaiveDate::from_ymd_opt(2025, 6, 30).unwrap();
196 let records =
197 gen.generate_jurisdiction_records(&["US".to_string(), "DE".to_string()], date);
198 assert_eq!(records.len(), 2);
199 }
200}