Skip to main content

datasynth_core/models/compliance/
jurisdiction.rs

1//! Jurisdiction-specific compliance profiles.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::standard_id::StandardId;
8use crate::models::graph_properties::{GraphPropertyValue, ToNodeProperties};
9
10/// Supranational body membership that propagates regulatory obligations.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum SupranationalBody {
14    /// European Union
15    Eu,
16    /// European Economic Area
17    Eea,
18    /// Eurozone (single currency)
19    Eurozone,
20    /// Association of Southeast Asian Nations
21    Asean,
22    /// Gulf Cooperation Council
23    Gcc,
24    /// Southern Common Market
25    Mercosur,
26}
27
28impl std::fmt::Display for SupranationalBody {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            Self::Eu => write!(f, "EU"),
32            Self::Eea => write!(f, "EEA"),
33            Self::Eurozone => write!(f, "Eurozone"),
34            Self::Asean => write!(f, "ASEAN"),
35            Self::Gcc => write!(f, "GCC"),
36            Self::Mercosur => write!(f, "Mercosur"),
37        }
38    }
39}
40
41/// Accounting framework for a jurisdiction.
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum JurisdictionAccountingFramework {
45    /// US GAAP (ASC Codification)
46    UsGaap,
47    /// Full IFRS adoption
48    Ifrs,
49    /// IFRS-converged local GAAP (e.g., Ind AS)
50    IfrsConverged,
51    /// Local GAAP with IFRS for listed entities
52    LocalGaapWithIfrs,
53    /// Local GAAP only
54    LocalGaap,
55}
56
57/// Audit framework for a jurisdiction.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "snake_case")]
60pub enum AuditFramework {
61    /// ISA (International Standards on Auditing) adopted directly
62    Isa,
63    /// ISA with local modifications (e.g., ISA (UK), ISA [DE])
64    IsaLocal,
65    /// PCAOB standards (US public companies)
66    Pcaob,
67    /// Local auditing standards (ISA-based)
68    LocalIsaBased,
69}
70
71/// Entity type for applicability criteria.
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(rename_all = "snake_case")]
74pub enum EntityType {
75    /// SEC-registered (US)
76    SecRegistrant,
77    /// Accelerated filer (US)
78    AcceleratedFiler,
79    /// Large accelerated filer (US)
80    LargeAcceleratedFiler,
81    /// Public interest entity (EU/intl)
82    PublicInterestEntity,
83    /// Listed on stock exchange
84    ListedEntity,
85    /// Large entity (above thresholds)
86    LargeEntity,
87    /// Small/medium enterprise
88    Sme,
89    /// Micro entity
90    MicroEntity,
91    /// Financial institution / bank
92    FinancialInstitution,
93}
94
95impl std::fmt::Display for EntityType {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            Self::SecRegistrant => write!(f, "SEC Registrant"),
99            Self::AcceleratedFiler => write!(f, "Accelerated Filer"),
100            Self::LargeAcceleratedFiler => write!(f, "Large Accelerated Filer"),
101            Self::PublicInterestEntity => write!(f, "Public Interest Entity"),
102            Self::ListedEntity => write!(f, "Listed Entity"),
103            Self::LargeEntity => write!(f, "Large Entity"),
104            Self::Sme => write!(f, "SME"),
105            Self::MicroEntity => write!(f, "Micro Entity"),
106            Self::FinancialInstitution => write!(f, "Financial Institution"),
107        }
108    }
109}
110
111/// How a standard applies in a specific jurisdiction.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct JurisdictionStandard {
114    /// Standard identifier
115    pub standard_id: StandardId,
116    /// Local effective date (may differ from global)
117    pub local_effective_date: Option<String>,
118    /// Local name or designation (e.g., "Ind AS 116" for IFRS 16 in India)
119    pub local_designation: Option<String>,
120    /// Applicability scope
121    pub applicability: Vec<EntityType>,
122}
123
124/// A jurisdiction's complete compliance profile.
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct JurisdictionProfile {
127    /// ISO 3166-1 alpha-2 country code
128    pub country_code: String,
129    /// Jurisdiction display name
130    pub country_name: String,
131    /// Supranational memberships
132    pub memberships: Vec<SupranationalBody>,
133    /// Primary accounting framework
134    pub accounting_framework: JurisdictionAccountingFramework,
135    /// Primary audit framework
136    pub audit_framework: AuditFramework,
137    /// Standards body name (e.g., "FASB", "DRSC", "ANC")
138    pub accounting_standards_body: String,
139    /// Audit oversight body (e.g., "PCAOB", "IDW", "H3C")
140    pub audit_oversight_body: String,
141    /// Securities regulator (e.g., "SEC", "BaFin", "AMF")
142    pub securities_regulator: Option<String>,
143    /// Stock exchanges
144    pub stock_exchanges: Vec<String>,
145    /// Mandatory standards in this jurisdiction
146    pub mandatory_standards: Vec<JurisdictionStandard>,
147    /// Corporate tax rate
148    pub corporate_tax_rate: Option<f64>,
149    /// Currency code (ISO 4217)
150    pub currency: String,
151    /// Whether IFRS is required for listed entities
152    pub ifrs_required_for_listed: bool,
153    /// Whether e-invoicing is mandatory
154    pub e_invoicing_mandatory: bool,
155    /// Required audit export format (e.g., "fec", "gobd")
156    pub audit_export_format: Option<String>,
157}
158
159impl JurisdictionProfile {
160    /// Creates a minimal jurisdiction profile.
161    pub fn new(
162        country_code: impl Into<String>,
163        country_name: impl Into<String>,
164        accounting_framework: JurisdictionAccountingFramework,
165        audit_framework: AuditFramework,
166        currency: impl Into<String>,
167    ) -> Self {
168        Self {
169            country_code: country_code.into(),
170            country_name: country_name.into(),
171            memberships: Vec::new(),
172            accounting_framework,
173            audit_framework,
174            accounting_standards_body: String::new(),
175            audit_oversight_body: String::new(),
176            securities_regulator: None,
177            stock_exchanges: Vec::new(),
178            mandatory_standards: Vec::new(),
179            corporate_tax_rate: None,
180            currency: currency.into(),
181            ifrs_required_for_listed: false,
182            e_invoicing_mandatory: false,
183            audit_export_format: None,
184        }
185    }
186
187    /// Returns true if this jurisdiction is an EU member.
188    pub fn is_eu_member(&self) -> bool {
189        self.memberships.contains(&SupranationalBody::Eu)
190    }
191
192    /// Returns true if this jurisdiction is in the Eurozone.
193    pub fn is_eurozone(&self) -> bool {
194        self.memberships.contains(&SupranationalBody::Eurozone)
195    }
196
197    /// Adds a supranational membership.
198    pub fn with_membership(mut self, body: SupranationalBody) -> Self {
199        self.memberships.push(body);
200        self
201    }
202
203    /// Adds a mandatory standard.
204    pub fn with_mandatory_standard(mut self, js: JurisdictionStandard) -> Self {
205        self.mandatory_standards.push(js);
206        self
207    }
208}
209
210impl ToNodeProperties for JurisdictionProfile {
211    fn node_type_name(&self) -> &'static str {
212        "jurisdiction_profile"
213    }
214    fn node_type_code(&self) -> u16 {
215        513
216    }
217    fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
218        let mut p = HashMap::new();
219        p.insert(
220            "countryCode".into(),
221            GraphPropertyValue::String(self.country_code.clone()),
222        );
223        p.insert(
224            "countryName".into(),
225            GraphPropertyValue::String(self.country_name.clone()),
226        );
227        p.insert(
228            "accountingFramework".into(),
229            GraphPropertyValue::String(format!("{:?}", self.accounting_framework)),
230        );
231        p.insert(
232            "auditFramework".into(),
233            GraphPropertyValue::String(format!("{:?}", self.audit_framework)),
234        );
235        p.insert(
236            "currency".into(),
237            GraphPropertyValue::String(self.currency.clone()),
238        );
239        if let Some(rate) = self.corporate_tax_rate {
240            p.insert("corporateTaxRate".into(), GraphPropertyValue::Float(rate));
241        }
242        p.insert(
243            "mandatoryStandardCount".into(),
244            GraphPropertyValue::Int(self.mandatory_standards.len() as i64),
245        );
246        p.insert(
247            "isEuMember".into(),
248            GraphPropertyValue::Bool(self.is_eu_member()),
249        );
250        p.insert(
251            "ifrsRequiredForListed".into(),
252            GraphPropertyValue::Bool(self.ifrs_required_for_listed),
253        );
254        if !self.memberships.is_empty() {
255            p.insert(
256                "memberships".into(),
257                GraphPropertyValue::StringList(
258                    self.memberships.iter().map(|m| m.to_string()).collect(),
259                ),
260            );
261        }
262        if !self.stock_exchanges.is_empty() {
263            p.insert(
264                "stockExchanges".into(),
265                GraphPropertyValue::StringList(self.stock_exchanges.clone()),
266            );
267        }
268        p
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_jurisdiction_profile_creation() {
278        let profile = JurisdictionProfile::new(
279            "DE",
280            "Federal Republic of Germany",
281            JurisdictionAccountingFramework::LocalGaapWithIfrs,
282            AuditFramework::IsaLocal,
283            "EUR",
284        )
285        .with_membership(SupranationalBody::Eu)
286        .with_membership(SupranationalBody::Eurozone);
287
288        assert!(profile.is_eu_member());
289        assert!(profile.is_eurozone());
290        assert_eq!(profile.country_code, "DE");
291    }
292}