Skip to main content

datasynth_core/models/compliance/
standard.rs

1//! Compliance standard metadata.
2
3use 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/// Category of compliance standard.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum StandardCategory {
16    /// Accounting standard (IFRS, US GAAP, local GAAP)
17    AccountingStandard,
18    /// Auditing standard (ISA, PCAOB AS)
19    AuditingStandard,
20    /// Regulatory requirement (SOX, EU Audit Regulation)
21    RegulatoryRequirement,
22    /// Reporting standard (XBRL, ESEF)
23    ReportingStandard,
24    /// Prudential regulation (Basel III/IV, Solvency II)
25    PrudentialRegulation,
26    /// Tax regulation (BEPS, CRS, FATCA)
27    TaxRegulation,
28    /// Data protection (GDPR, CCPA)
29    DataProtection,
30    /// Sustainability standard (CSRD, ISSB, GRI)
31    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/// Domain within financial compliance.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
51#[serde(rename_all = "snake_case")]
52pub enum ComplianceDomain {
53    /// Financial reporting and accounting
54    FinancialReporting,
55    /// Internal control over financial reporting
56    InternalControl,
57    /// External audit procedures
58    ExternalAudit,
59    /// Tax compliance and reporting
60    TaxCompliance,
61    /// Regulatory reporting to authorities
62    RegulatoryReporting,
63    /// Enterprise risk management
64    RiskManagement,
65    /// Data governance and protection
66    DataGovernance,
67    /// ESG and sustainability reporting
68    Sustainability,
69    /// Anti-money laundering
70    AntiMoneyLaundering,
71    /// Prudential capital and liquidity
72    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/// Issuing body for a compliance standard.
93#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
94#[serde(rename_all = "snake_case")]
95pub enum IssuingBody {
96    /// International Accounting Standards Board
97    Iasb,
98    /// International Auditing and Assurance Standards Board
99    Iaasb,
100    /// Financial Accounting Standards Board (US)
101    Fasb,
102    /// Public Company Accounting Oversight Board (US)
103    Pcaob,
104    /// Securities and Exchange Commission (US)
105    Sec,
106    /// Basel Committee on Banking Supervision
107    Bcbs,
108    /// European Commission / Parliament
109    EuropeanUnion,
110    /// Financial Reporting Council (UK)
111    Frc,
112    /// Deutsches Rechnungslegungs Standards Committee
113    Drsc,
114    /// Institut der Wirtschaftsprüfer (Germany)
115    Idw,
116    /// Autorité des Normes Comptables (France)
117    Anc,
118    /// Accounting Standards Board of Japan
119    Asbj,
120    /// Institute of Chartered Accountants of India
121    Icai,
122    /// International Sustainability Standards Board
123    Issb,
124    /// Organisation for Economic Cooperation and Development
125    Oecd,
126    /// User-defined or other body
127    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/// A requirement within a compliance standard.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct StandardRequirement {
156    /// Requirement identifier (e.g., "ISA-315.R14")
157    pub id: String,
158    /// Requirement title
159    pub title: String,
160    /// Description of the requirement
161    pub description: String,
162    /// Related audit assertions
163    pub assertions: Vec<String>,
164}
165
166/// A compliance standard with full metadata.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ComplianceStandard {
169    /// Canonical identifier
170    pub id: StandardId,
171    /// Human-readable title
172    pub title: String,
173    /// Issuing body
174    pub issuing_body: IssuingBody,
175    /// Standard category
176    pub category: StandardCategory,
177    /// Domain area
178    pub domain: ComplianceDomain,
179    /// All known versions with effective dates
180    pub versions: Vec<TemporalVersion>,
181    /// Standards this one supersedes
182    pub supersedes: Vec<StandardId>,
183    /// Standard this one is superseded by (if any)
184    pub superseded_by: Option<StandardId>,
185    /// Cross-references to related standards
186    pub cross_references: Vec<CrossReference>,
187    /// Jurisdictions where this standard is mandatory (ISO 3166-1 alpha-2)
188    pub mandatory_jurisdictions: Vec<String>,
189    /// Jurisdictions where this standard is permitted but optional
190    pub permitted_jurisdictions: Vec<String>,
191    /// Key requirements within this standard
192    pub requirements: Vec<StandardRequirement>,
193    /// GL account types this standard applies to (e.g., "Revenue", "Leases", "PP&E")
194    #[serde(default)]
195    pub applicable_account_types: Vec<String>,
196    /// Business processes this standard governs (e.g., "O2C", "P2P", "R2R")
197    #[serde(default)]
198    pub applicable_processes: Vec<String>,
199}
200
201impl ComplianceStandard {
202    /// Creates a new compliance standard with minimal required fields.
203    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    /// Adds a temporal version.
229    pub fn with_version(mut self, version: TemporalVersion) -> Self {
230        self.versions.push(version);
231        self
232    }
233
234    /// Adds a superseded standard.
235    pub fn supersedes_standard(mut self, id: StandardId) -> Self {
236        self.supersedes.push(id);
237        self
238    }
239
240    /// Sets the superseding standard.
241    pub fn superseded_by_standard(mut self, id: StandardId) -> Self {
242        self.superseded_by = Some(id);
243        self
244    }
245
246    /// Adds a cross-reference.
247    pub fn with_cross_reference(mut self, xref: CrossReference) -> Self {
248        self.cross_references.push(xref);
249        self
250    }
251
252    /// Adds a mandatory jurisdiction.
253    pub fn mandatory_in(mut self, country_code: &str) -> Self {
254        self.mandatory_jurisdictions.push(country_code.to_string());
255        self
256    }
257
258    /// Adds a requirement.
259    pub fn with_requirement(mut self, req: StandardRequirement) -> Self {
260        self.requirements.push(req);
261        self
262    }
263
264    /// Adds an applicable GL account type.
265    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    /// Adds applicable GL account types in bulk.
271    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    /// Adds an applicable business process.
278    pub fn with_process(mut self, process: &str) -> Self {
279        self.applicable_processes.push(process.to_string());
280        self
281    }
282
283    /// Adds applicable business processes in bulk.
284    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    /// Returns true if this standard is currently superseded.
291    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}