1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::cross_reference::CrossReference;
8use super::standard_id::StandardId;
9use super::temporal::TemporalVersion;
10use crate::models::graph_properties::{GraphPropertyValue, ToNodeProperties};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum StandardCategory {
16 AccountingStandard,
18 AuditingStandard,
20 RegulatoryRequirement,
22 ReportingStandard,
24 PrudentialRegulation,
26 TaxRegulation,
28 DataProtection,
30 SustainabilityStandard,
32}
33
34impl std::fmt::Display for StandardCategory {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Self::AccountingStandard => write!(f, "Accounting Standard"),
38 Self::AuditingStandard => write!(f, "Auditing Standard"),
39 Self::RegulatoryRequirement => write!(f, "Regulatory Requirement"),
40 Self::ReportingStandard => write!(f, "Reporting Standard"),
41 Self::PrudentialRegulation => write!(f, "Prudential Regulation"),
42 Self::TaxRegulation => write!(f, "Tax Regulation"),
43 Self::DataProtection => write!(f, "Data Protection"),
44 Self::SustainabilityStandard => write!(f, "Sustainability Standard"),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum ComplianceDomain {
53 FinancialReporting,
55 InternalControl,
57 ExternalAudit,
59 TaxCompliance,
61 RegulatoryReporting,
63 RiskManagement,
65 DataGovernance,
67 Sustainability,
69 AntiMoneyLaundering,
71 PrudentialCapital,
73}
74
75impl std::fmt::Display for ComplianceDomain {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 Self::FinancialReporting => write!(f, "Financial Reporting"),
79 Self::InternalControl => write!(f, "Internal Control"),
80 Self::ExternalAudit => write!(f, "External Audit"),
81 Self::TaxCompliance => write!(f, "Tax Compliance"),
82 Self::RegulatoryReporting => write!(f, "Regulatory Reporting"),
83 Self::RiskManagement => write!(f, "Risk Management"),
84 Self::DataGovernance => write!(f, "Data Governance"),
85 Self::Sustainability => write!(f, "Sustainability"),
86 Self::AntiMoneyLaundering => write!(f, "Anti-Money Laundering"),
87 Self::PrudentialCapital => write!(f, "Prudential Capital"),
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
94#[serde(rename_all = "snake_case")]
95pub enum IssuingBody {
96 Iasb,
98 Iaasb,
100 Fasb,
102 Pcaob,
104 Sec,
106 Bcbs,
108 EuropeanUnion,
110 Frc,
112 Drsc,
114 Idw,
116 Anc,
118 Asbj,
120 Icai,
122 Issb,
124 Oecd,
126 Custom(String),
128}
129
130impl std::fmt::Display for IssuingBody {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 match self {
133 Self::Iasb => write!(f, "IASB"),
134 Self::Iaasb => write!(f, "IAASB"),
135 Self::Fasb => write!(f, "FASB"),
136 Self::Pcaob => write!(f, "PCAOB"),
137 Self::Sec => write!(f, "SEC"),
138 Self::Bcbs => write!(f, "BCBS"),
139 Self::EuropeanUnion => write!(f, "EU"),
140 Self::Frc => write!(f, "FRC"),
141 Self::Drsc => write!(f, "DRSC"),
142 Self::Idw => write!(f, "IDW"),
143 Self::Anc => write!(f, "ANC"),
144 Self::Asbj => write!(f, "ASBJ"),
145 Self::Icai => write!(f, "ICAI"),
146 Self::Issb => write!(f, "ISSB"),
147 Self::Oecd => write!(f, "OECD"),
148 Self::Custom(s) => write!(f, "{s}"),
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct StandardRequirement {
156 pub id: String,
158 pub title: String,
160 pub description: String,
162 pub assertions: Vec<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ComplianceStandard {
169 pub id: StandardId,
171 pub title: String,
173 pub issuing_body: IssuingBody,
175 pub category: StandardCategory,
177 pub domain: ComplianceDomain,
179 pub versions: Vec<TemporalVersion>,
181 pub supersedes: Vec<StandardId>,
183 pub superseded_by: Option<StandardId>,
185 pub cross_references: Vec<CrossReference>,
187 pub mandatory_jurisdictions: Vec<String>,
189 pub permitted_jurisdictions: Vec<String>,
191 pub requirements: Vec<StandardRequirement>,
193 #[serde(default)]
195 pub applicable_account_types: Vec<String>,
196 #[serde(default)]
198 pub applicable_processes: Vec<String>,
199}
200
201impl ComplianceStandard {
202 pub fn new(
204 id: StandardId,
205 title: impl Into<String>,
206 issuing_body: IssuingBody,
207 category: StandardCategory,
208 domain: ComplianceDomain,
209 ) -> Self {
210 Self {
211 id,
212 title: title.into(),
213 issuing_body,
214 category,
215 domain,
216 versions: Vec::new(),
217 supersedes: Vec::new(),
218 superseded_by: None,
219 cross_references: Vec::new(),
220 mandatory_jurisdictions: Vec::new(),
221 permitted_jurisdictions: Vec::new(),
222 requirements: Vec::new(),
223 applicable_account_types: Vec::new(),
224 applicable_processes: Vec::new(),
225 }
226 }
227
228 pub fn with_version(mut self, version: TemporalVersion) -> Self {
230 self.versions.push(version);
231 self
232 }
233
234 pub fn supersedes_standard(mut self, id: StandardId) -> Self {
236 self.supersedes.push(id);
237 self
238 }
239
240 pub fn superseded_by_standard(mut self, id: StandardId) -> Self {
242 self.superseded_by = Some(id);
243 self
244 }
245
246 pub fn with_cross_reference(mut self, xref: CrossReference) -> Self {
248 self.cross_references.push(xref);
249 self
250 }
251
252 pub fn mandatory_in(mut self, country_code: &str) -> Self {
254 self.mandatory_jurisdictions.push(country_code.to_string());
255 self
256 }
257
258 pub fn with_requirement(mut self, req: StandardRequirement) -> Self {
260 self.requirements.push(req);
261 self
262 }
263
264 pub fn with_account_type(mut self, account_type: &str) -> Self {
266 self.applicable_account_types.push(account_type.to_string());
267 self
268 }
269
270 pub fn with_account_types(mut self, types: &[&str]) -> Self {
272 self.applicable_account_types
273 .extend(types.iter().map(|s| s.to_string()));
274 self
275 }
276
277 pub fn with_process(mut self, process: &str) -> Self {
279 self.applicable_processes.push(process.to_string());
280 self
281 }
282
283 pub fn with_processes(mut self, processes: &[&str]) -> Self {
285 self.applicable_processes
286 .extend(processes.iter().map(|s| s.to_string()));
287 self
288 }
289
290 pub fn is_superseded(&self) -> bool {
292 self.superseded_by.is_some()
293 }
294}
295
296impl ToNodeProperties for ComplianceStandard {
297 fn node_type_name(&self) -> &'static str {
298 "compliance_standard"
299 }
300 fn node_type_code(&self) -> u16 {
301 510
302 }
303 fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
304 let mut p = HashMap::new();
305 p.insert(
306 "standardId".into(),
307 GraphPropertyValue::String(self.id.as_str().to_string()),
308 );
309 p.insert(
310 "title".into(),
311 GraphPropertyValue::String(self.title.clone()),
312 );
313 p.insert(
314 "issuingBody".into(),
315 GraphPropertyValue::String(self.issuing_body.to_string()),
316 );
317 p.insert(
318 "category".into(),
319 GraphPropertyValue::String(self.category.to_string()),
320 );
321 p.insert(
322 "domain".into(),
323 GraphPropertyValue::String(self.domain.to_string()),
324 );
325 p.insert(
326 "isSuperseded".into(),
327 GraphPropertyValue::Bool(self.is_superseded()),
328 );
329 p.insert(
330 "mandatoryJurisdictions".into(),
331 GraphPropertyValue::StringList(self.mandatory_jurisdictions.clone()),
332 );
333 if !self.applicable_account_types.is_empty() {
334 p.insert(
335 "applicableAccountTypes".into(),
336 GraphPropertyValue::StringList(self.applicable_account_types.clone()),
337 );
338 }
339 if !self.applicable_processes.is_empty() {
340 p.insert(
341 "applicableProcesses".into(),
342 GraphPropertyValue::StringList(self.applicable_processes.clone()),
343 );
344 }
345 p.insert(
346 "requirementCount".into(),
347 GraphPropertyValue::Int(self.requirements.len() as i64),
348 );
349 p.insert(
350 "versionCount".into(),
351 GraphPropertyValue::Int(self.versions.len() as i64),
352 );
353 p
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn test_compliance_standard_creation() {
363 let std = ComplianceStandard::new(
364 StandardId::new("IFRS", "16"),
365 "Leases",
366 IssuingBody::Iasb,
367 StandardCategory::AccountingStandard,
368 ComplianceDomain::FinancialReporting,
369 )
370 .mandatory_in("DE")
371 .mandatory_in("GB");
372
373 assert_eq!(std.id.as_str(), "IFRS-16");
374 assert_eq!(std.mandatory_jurisdictions.len(), 2);
375 assert!(!std.is_superseded());
376 }
377}