Skip to main content

datasynth_standards/
framework.rs

1//! Accounting Framework Selection and Configuration.
2//!
3//! Provides the core types for selecting between US GAAP and IFRS accounting
4//! frameworks, along with framework-specific settings that control how
5//! accounting standards are applied during synthetic data generation.
6
7use serde::{Deserialize, Serialize};
8
9/// Primary accounting framework selection.
10///
11/// Determines which set of accounting standards governs the generation
12/// of financial data, affecting everything from revenue recognition
13/// timing to lease classification.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
15#[serde(rename_all = "snake_case")]
16pub enum AccountingFramework {
17    /// United States Generally Accepted Accounting Principles.
18    ///
19    /// Key characteristics:
20    /// - Rules-based approach
21    /// - LIFO inventory permitted
22    /// - No revaluation of PPE above cost
23    /// - No reversal of impairment losses (except for certain assets)
24    /// - Bright-line tests for lease classification
25    #[default]
26    UsGaap,
27
28    /// International Financial Reporting Standards.
29    ///
30    /// Key characteristics:
31    /// - Principles-based approach
32    /// - LIFO inventory prohibited
33    /// - Revaluation model permitted for PPE
34    /// - Reversal of impairment losses permitted (except goodwill)
35    /// - Principles-based lease classification
36    Ifrs,
37
38    /// Dual Reporting under both US GAAP and IFRS.
39    ///
40    /// Generates reconciliation data showing differences between
41    /// the two frameworks for the same underlying transactions.
42    DualReporting,
43}
44
45impl AccountingFramework {
46    /// Returns the standard name for revenue recognition.
47    pub fn revenue_standard(&self) -> &'static str {
48        match self {
49            Self::UsGaap => "ASC 606",
50            Self::Ifrs => "IFRS 15",
51            Self::DualReporting => "ASC 606 / IFRS 15",
52        }
53    }
54
55    /// Returns the standard name for lease accounting.
56    pub fn lease_standard(&self) -> &'static str {
57        match self {
58            Self::UsGaap => "ASC 842",
59            Self::Ifrs => "IFRS 16",
60            Self::DualReporting => "ASC 842 / IFRS 16",
61        }
62    }
63
64    /// Returns the standard name for fair value measurement.
65    pub fn fair_value_standard(&self) -> &'static str {
66        match self {
67            Self::UsGaap => "ASC 820",
68            Self::Ifrs => "IFRS 13",
69            Self::DualReporting => "ASC 820 / IFRS 13",
70        }
71    }
72
73    /// Returns the standard name for impairment.
74    pub fn impairment_standard(&self) -> &'static str {
75        match self {
76            Self::UsGaap => "ASC 360",
77            Self::Ifrs => "IAS 36",
78            Self::DualReporting => "ASC 360 / IAS 36",
79        }
80    }
81
82    /// Returns whether LIFO inventory costing is permitted.
83    pub fn allows_lifo(&self) -> bool {
84        matches!(self, Self::UsGaap)
85    }
86
87    /// Returns whether development cost capitalization is required.
88    pub fn requires_development_capitalization(&self) -> bool {
89        matches!(self, Self::Ifrs | Self::DualReporting)
90    }
91
92    /// Returns whether PPE revaluation above cost is permitted.
93    pub fn allows_ppe_revaluation(&self) -> bool {
94        matches!(self, Self::Ifrs | Self::DualReporting)
95    }
96
97    /// Returns whether impairment loss reversal is permitted.
98    pub fn allows_impairment_reversal(&self) -> bool {
99        matches!(self, Self::Ifrs | Self::DualReporting)
100    }
101
102    /// Returns whether this framework uses bright-line lease tests.
103    pub fn uses_brightline_lease_tests(&self) -> bool {
104        matches!(self, Self::UsGaap)
105    }
106}
107
108impl std::fmt::Display for AccountingFramework {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            Self::UsGaap => write!(f, "US GAAP"),
112            Self::Ifrs => write!(f, "IFRS"),
113            Self::DualReporting => write!(f, "Dual Reporting (US GAAP & IFRS)"),
114        }
115    }
116}
117
118/// Framework-specific settings that control accounting treatment options.
119///
120/// These settings allow fine-grained control over framework-specific
121/// accounting policies within the selected framework.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct FrameworkSettings {
124    /// The primary accounting framework.
125    pub framework: AccountingFramework,
126
127    /// Whether to use LIFO inventory costing (US GAAP only).
128    ///
129    /// Default: false (use FIFO/weighted average)
130    #[serde(default)]
131    pub use_lifo_inventory: bool,
132
133    /// Whether to capitalize development costs (IFRS requirement, US GAAP option).
134    ///
135    /// Under IFRS, development costs must be capitalized when criteria are met.
136    /// Under US GAAP, most development costs are expensed.
137    #[serde(default)]
138    pub capitalize_development_costs: bool,
139
140    /// Whether to use revaluation model for PPE (IFRS option).
141    ///
142    /// Under IFRS, entities can choose between cost model and revaluation model.
143    /// Under US GAAP, revaluation above cost is not permitted.
144    #[serde(default)]
145    pub use_ppe_revaluation: bool,
146
147    /// Whether to reverse impairment losses when conditions improve (IFRS option).
148    ///
149    /// Under IFRS, impairment losses (except for goodwill) can be reversed.
150    /// Under US GAAP, impairment losses generally cannot be reversed.
151    #[serde(default)]
152    pub allow_impairment_reversal: bool,
153
154    /// Threshold percentage for lease term test (US GAAP: 75%).
155    ///
156    /// A lease is classified as finance/capital if the lease term is >= this
157    /// percentage of the asset's economic life.
158    #[serde(default = "default_lease_term_threshold")]
159    pub lease_term_threshold: f64,
160
161    /// Threshold percentage for present value test (US GAAP: 90%).
162    ///
163    /// A lease is classified as finance/capital if the present value of lease
164    /// payments is >= this percentage of the asset's fair value.
165    #[serde(default = "default_lease_pv_threshold")]
166    pub lease_pv_threshold: f64,
167
168    /// Default incremental borrowing rate for lease calculations.
169    #[serde(default = "default_incremental_borrowing_rate")]
170    pub default_incremental_borrowing_rate: f64,
171
172    /// Revenue recognition constraint for variable consideration.
173    ///
174    /// Under both frameworks, variable consideration is constrained to the
175    /// amount that is highly probable (IFRS) or probable (US GAAP) not to
176    /// result in a significant revenue reversal.
177    #[serde(default = "default_variable_consideration_constraint")]
178    pub variable_consideration_constraint: f64,
179}
180
181fn default_lease_term_threshold() -> f64 {
182    0.75
183}
184
185fn default_lease_pv_threshold() -> f64 {
186    0.90
187}
188
189fn default_incremental_borrowing_rate() -> f64 {
190    0.05
191}
192
193fn default_variable_consideration_constraint() -> f64 {
194    0.80
195}
196
197impl Default for FrameworkSettings {
198    fn default() -> Self {
199        Self {
200            framework: AccountingFramework::default(),
201            use_lifo_inventory: false,
202            capitalize_development_costs: false,
203            use_ppe_revaluation: false,
204            allow_impairment_reversal: false,
205            lease_term_threshold: default_lease_term_threshold(),
206            lease_pv_threshold: default_lease_pv_threshold(),
207            default_incremental_borrowing_rate: default_incremental_borrowing_rate(),
208            variable_consideration_constraint: default_variable_consideration_constraint(),
209        }
210    }
211}
212
213impl FrameworkSettings {
214    /// Create settings for US GAAP with typical US company policies.
215    pub fn us_gaap() -> Self {
216        Self {
217            framework: AccountingFramework::UsGaap,
218            use_lifo_inventory: false, // Most companies use FIFO
219            capitalize_development_costs: false,
220            use_ppe_revaluation: false,
221            allow_impairment_reversal: false,
222            ..Default::default()
223        }
224    }
225
226    /// Create settings for IFRS with typical international policies.
227    pub fn ifrs() -> Self {
228        Self {
229            framework: AccountingFramework::Ifrs,
230            use_lifo_inventory: false,          // LIFO prohibited
231            capitalize_development_costs: true, // Required when criteria met
232            use_ppe_revaluation: false,         // Optional, most use cost model
233            allow_impairment_reversal: true,    // Permitted under IFRS
234            ..Default::default()
235        }
236    }
237
238    /// Create settings for dual reporting.
239    pub fn dual_reporting() -> Self {
240        Self {
241            framework: AccountingFramework::DualReporting,
242            use_lifo_inventory: false,
243            capitalize_development_costs: true,
244            use_ppe_revaluation: false,
245            allow_impairment_reversal: true,
246            ..Default::default()
247        }
248    }
249
250    /// Validate settings are consistent with the selected framework.
251    pub fn validate(&self) -> Result<(), FrameworkValidationError> {
252        // LIFO is only permitted under US GAAP
253        if self.use_lifo_inventory && self.framework == AccountingFramework::Ifrs {
254            return Err(FrameworkValidationError::LifoNotPermittedUnderIfrs);
255        }
256
257        // PPE revaluation is not permitted under US GAAP
258        if self.use_ppe_revaluation && self.framework == AccountingFramework::UsGaap {
259            return Err(FrameworkValidationError::RevaluationNotPermittedUnderUsGaap);
260        }
261
262        // Impairment reversal is not permitted under US GAAP
263        if self.allow_impairment_reversal && self.framework == AccountingFramework::UsGaap {
264            return Err(FrameworkValidationError::ImpairmentReversalNotPermittedUnderUsGaap);
265        }
266
267        // Validate thresholds
268        if !(0.0..=1.0).contains(&self.lease_term_threshold) {
269            return Err(FrameworkValidationError::InvalidThreshold(
270                "lease_term_threshold".to_string(),
271            ));
272        }
273
274        if !(0.0..=1.0).contains(&self.lease_pv_threshold) {
275            return Err(FrameworkValidationError::InvalidThreshold(
276                "lease_pv_threshold".to_string(),
277            ));
278        }
279
280        Ok(())
281    }
282}
283
284/// Errors that can occur during framework settings validation.
285#[derive(Debug, Clone, thiserror::Error)]
286pub enum FrameworkValidationError {
287    #[error("LIFO inventory costing is not permitted under IFRS")]
288    LifoNotPermittedUnderIfrs,
289
290    #[error("PPE revaluation above cost is not permitted under US GAAP")]
291    RevaluationNotPermittedUnderUsGaap,
292
293    #[error("Reversal of impairment losses is not permitted under US GAAP")]
294    ImpairmentReversalNotPermittedUnderUsGaap,
295
296    #[error("Invalid threshold value for {0}: must be between 0.0 and 1.0")]
297    InvalidThreshold(String),
298}
299
300/// Key differences between US GAAP and IFRS for a specific area.
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct FrameworkDifference {
303    /// Area of accounting (e.g., "Revenue Recognition", "Lease Classification").
304    pub area: String,
305
306    /// US GAAP treatment description.
307    pub us_gaap_treatment: String,
308
309    /// IFRS treatment description.
310    pub ifrs_treatment: String,
311
312    /// Whether this difference typically results in material differences.
313    pub typically_material: bool,
314
315    /// Relevant US GAAP codification reference.
316    pub us_gaap_reference: String,
317
318    /// Relevant IFRS standard reference.
319    pub ifrs_reference: String,
320}
321
322impl FrameworkDifference {
323    /// Returns common framework differences for educational/documentation purposes.
324    pub fn common_differences() -> Vec<Self> {
325        vec![
326            Self {
327                area: "Inventory Costing".to_string(),
328                us_gaap_treatment: "LIFO, FIFO, and weighted average permitted".to_string(),
329                ifrs_treatment: "LIFO prohibited; FIFO and weighted average permitted".to_string(),
330                typically_material: true,
331                us_gaap_reference: "ASC 330".to_string(),
332                ifrs_reference: "IAS 2".to_string(),
333            },
334            Self {
335                area: "Development Costs".to_string(),
336                us_gaap_treatment: "Generally expensed as incurred".to_string(),
337                ifrs_treatment: "Capitalized when specified criteria are met".to_string(),
338                typically_material: true,
339                us_gaap_reference: "ASC 730".to_string(),
340                ifrs_reference: "IAS 38".to_string(),
341            },
342            Self {
343                area: "Property, Plant & Equipment".to_string(),
344                us_gaap_treatment: "Cost model only; no revaluation above cost".to_string(),
345                ifrs_treatment: "Cost model or revaluation model permitted".to_string(),
346                typically_material: true,
347                us_gaap_reference: "ASC 360".to_string(),
348                ifrs_reference: "IAS 16".to_string(),
349            },
350            Self {
351                area: "Impairment Reversal".to_string(),
352                us_gaap_treatment: "Not permitted for most assets".to_string(),
353                ifrs_treatment: "Permitted except for goodwill".to_string(),
354                typically_material: true,
355                us_gaap_reference: "ASC 360".to_string(),
356                ifrs_reference: "IAS 36".to_string(),
357            },
358            Self {
359                area: "Lease Classification".to_string(),
360                us_gaap_treatment: "Bright-line tests (75% term, 90% PV)".to_string(),
361                ifrs_treatment: "Principles-based; transfer of risks and rewards".to_string(),
362                typically_material: false,
363                us_gaap_reference: "ASC 842".to_string(),
364                ifrs_reference: "IFRS 16".to_string(),
365            },
366            Self {
367                area: "Contingent Liabilities".to_string(),
368                us_gaap_treatment: "Recognized when probable (>75%) and estimable".to_string(),
369                ifrs_treatment: "Recognized when probable (>50%) and estimable".to_string(),
370                typically_material: true,
371                us_gaap_reference: "ASC 450".to_string(),
372                ifrs_reference: "IAS 37".to_string(),
373            },
374        ]
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_framework_defaults() {
384        let framework = AccountingFramework::default();
385        assert_eq!(framework, AccountingFramework::UsGaap);
386    }
387
388    #[test]
389    fn test_framework_standards() {
390        assert_eq!(AccountingFramework::UsGaap.revenue_standard(), "ASC 606");
391        assert_eq!(AccountingFramework::Ifrs.revenue_standard(), "IFRS 15");
392        assert_eq!(AccountingFramework::UsGaap.lease_standard(), "ASC 842");
393        assert_eq!(AccountingFramework::Ifrs.lease_standard(), "IFRS 16");
394    }
395
396    #[test]
397    fn test_framework_features() {
398        assert!(AccountingFramework::UsGaap.allows_lifo());
399        assert!(!AccountingFramework::Ifrs.allows_lifo());
400
401        assert!(!AccountingFramework::UsGaap.allows_ppe_revaluation());
402        assert!(AccountingFramework::Ifrs.allows_ppe_revaluation());
403
404        assert!(!AccountingFramework::UsGaap.allows_impairment_reversal());
405        assert!(AccountingFramework::Ifrs.allows_impairment_reversal());
406    }
407
408    #[test]
409    fn test_settings_validation_us_gaap() {
410        let settings = FrameworkSettings::us_gaap();
411        assert!(settings.validate().is_ok());
412    }
413
414    #[test]
415    fn test_settings_validation_ifrs() {
416        let settings = FrameworkSettings::ifrs();
417        assert!(settings.validate().is_ok());
418    }
419
420    #[test]
421    fn test_settings_validation_lifo_under_ifrs() {
422        let mut settings = FrameworkSettings::ifrs();
423        settings.use_lifo_inventory = true;
424        assert!(matches!(
425            settings.validate(),
426            Err(FrameworkValidationError::LifoNotPermittedUnderIfrs)
427        ));
428    }
429
430    #[test]
431    fn test_settings_validation_revaluation_under_us_gaap() {
432        let mut settings = FrameworkSettings::us_gaap();
433        settings.use_ppe_revaluation = true;
434        assert!(matches!(
435            settings.validate(),
436            Err(FrameworkValidationError::RevaluationNotPermittedUnderUsGaap)
437        ));
438    }
439
440    #[test]
441    fn test_common_differences() {
442        let differences = FrameworkDifference::common_differences();
443        assert!(!differences.is_empty());
444        assert!(differences.iter().any(|d| d.area == "Inventory Costing"));
445    }
446
447    #[test]
448    fn test_serde_roundtrip() {
449        let framework = AccountingFramework::Ifrs;
450        let json = serde_json::to_string(&framework).unwrap();
451        let deserialized: AccountingFramework = serde_json::from_str(&json).unwrap();
452        assert_eq!(framework, deserialized);
453
454        let settings = FrameworkSettings::ifrs();
455        let json = serde_json::to_string(&settings).unwrap();
456        let deserialized: FrameworkSettings = serde_json::from_str(&json).unwrap();
457        assert_eq!(settings.framework, deserialized.framework);
458    }
459}