Skip to main content

datasynth_core/models/audit/
accounting_estimates.rs

1//! Accounting estimates models — ISA 540.
2//!
3//! Accounting estimates are approximations of monetary amounts in the
4//! absence of a precise means of measurement. ISA 540 (Revised 2019)
5//! requires auditors to evaluate the reasonableness of management's
6//! estimates and identify risks of material misstatement.
7//!
8//! Key estimate types covered:
9//! - Deferred tax provisions (IAS 12 / ASC 740)
10//! - Expected credit losses (IFRS 9 / ASC 326)
11//! - Pension obligations (IAS 19 / ASC 715)
12//! - Fair value measurements (IFRS 13 / ASC 820)
13//! - Impairment tests (IAS 36 / ASC 350–360)
14//! - Provisions for liabilities (IAS 37 / ASC 450)
15//! - Share-based payments (IFRS 2 / ASC 718)
16//! - Depreciation useful-life assessments
17
18use rust_decimal::Decimal;
19use serde::{Deserialize, Serialize};
20
21// ---------------------------------------------------------------------------
22// Enums
23// ---------------------------------------------------------------------------
24
25/// Category of accounting estimate.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum EstimateType {
29    /// Deferred tax asset/liability recognition (IAS 12 / ASC 740).
30    DeferredTaxProvision,
31    /// Expected credit loss allowance (IFRS 9 / ASC 326).
32    ExpectedCreditLoss,
33    /// Defined benefit pension obligation (IAS 19 / ASC 715).
34    PensionObligation,
35    /// Level 2/3 fair value measurement (IFRS 13 / ASC 820).
36    FairValueMeasurement,
37    /// Goodwill or long-lived asset impairment test (IAS 36 / ASC 350–360).
38    ImpairmentTest,
39    /// Provision for legal/environmental/warranty liabilities (IAS 37 / ASC 450).
40    ProvisionForLiabilities,
41    /// Share-based payment expense (IFRS 2 / ASC 718).
42    ShareBasedPayment,
43    /// Useful-life revision for property, plant & equipment (IAS 16 / ASC 360).
44    DepreciationUsefulLife,
45}
46
47impl std::fmt::Display for EstimateType {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        let s = match self {
50            Self::DeferredTaxProvision => "Deferred Tax Provision",
51            Self::ExpectedCreditLoss => "Expected Credit Loss",
52            Self::PensionObligation => "Pension Obligation",
53            Self::FairValueMeasurement => "Fair Value Measurement",
54            Self::ImpairmentTest => "Impairment Test",
55            Self::ProvisionForLiabilities => "Provision for Liabilities",
56            Self::ShareBasedPayment => "Share-Based Payment",
57            Self::DepreciationUsefulLife => "Depreciation Useful Life",
58        };
59        write!(f, "{s}")
60    }
61}
62
63/// Degree of uncertainty inherent in an accounting estimate.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum UncertaintyLevel {
67    /// Outcome is predictable with reasonable confidence.
68    Low,
69    /// Outcome involves some uncertainty; range of outcomes is limited.
70    Medium,
71    /// Outcome is highly sensitive to assumptions; wide range of outcomes possible.
72    High,
73}
74
75/// Complexity of the estimation process.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum EstimateComplexity {
79    /// Standard actuarial or formulaic calculation; limited judgment required.
80    Simple,
81    /// Multiple interdependent inputs; moderate management judgment required.
82    Moderate,
83    /// Sophisticated models, specialist input, or significant management judgment.
84    Complex,
85}
86
87/// Auditor's assessment of an individual key assumption.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum AssumptionAssessment {
91    /// Assumption falls within an acceptable range given the entity's circumstances.
92    Reasonable,
93    /// Assumption is at the favourable end of an acceptable range.
94    Optimistic,
95    /// Assumption is outside or at the extreme end of an acceptable range.
96    Aggressive,
97}
98
99/// Degree of subjectivity in a key assumption.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "snake_case")]
102pub enum SubjectivityLevel {
103    /// Observable market data or contractual terms determine the assumption.
104    Low,
105    /// Assumptions are internally derived but corroborated by external evidence.
106    Medium,
107    /// Assumptions rely primarily on management intent or unobservable inputs.
108    High,
109}
110
111// ---------------------------------------------------------------------------
112// Supporting structs
113// ---------------------------------------------------------------------------
114
115/// A key assumption underlying an accounting estimate.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct EstimateAssumption {
118    /// Plain-language description of the assumption (e.g. "discount rate 4.5 %").
119    pub description: String,
120    /// Sensitivity of the estimate to a 1-unit change in this assumption
121    /// (expressed as absolute monetary impact).
122    #[serde(with = "crate::serde_decimal")]
123    pub sensitivity: Decimal,
124    /// Auditor's assessment of the assumption's reasonableness.
125    pub reasonableness: AssumptionAssessment,
126}
127
128/// ISA 540 risk factor indicators for the estimate.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct Isa540RiskFactors {
131    /// Estimation uncertainty indicator per ISA 540.
132    pub estimation_uncertainty: UncertaintyLevel,
133    /// Complexity of the estimation process.
134    pub complexity: EstimateComplexity,
135    /// Degree of management subjectivity in key assumptions.
136    pub subjectivity: SubjectivityLevel,
137}
138
139/// Retrospective review comparing the prior-period estimate to the actual outcome.
140///
141/// ISA 540 requires auditors to review management's prior-period estimates
142/// to detect indications of possible management bias.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct RetrospectiveReview {
145    /// Management's estimate at the prior period-end.
146    #[serde(with = "crate::serde_decimal")]
147    pub prior_period_estimate: Decimal,
148    /// Actual outcome observed in the current period.
149    #[serde(with = "crate::serde_decimal")]
150    pub actual_outcome: Decimal,
151    /// Monetary variance (actual − estimate).
152    #[serde(with = "crate::serde_decimal")]
153    pub variance: Decimal,
154    /// Variance expressed as a percentage of the prior-period estimate.
155    #[serde(with = "crate::serde_decimal")]
156    pub variance_percentage: Decimal,
157    /// `true` if the direction of variance suggests consistent management bias
158    /// (e.g. estimate consistently overstated vs actual).
159    pub management_bias_indicator: bool,
160}
161
162// ---------------------------------------------------------------------------
163// Primary model
164// ---------------------------------------------------------------------------
165
166/// A single accounting estimate reviewed under ISA 540.
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct AccountingEstimate {
169    /// Unique identifier for this estimate.
170    pub id: String,
171    /// Entity code of the reporting entity.
172    pub entity_code: String,
173    /// Type of accounting estimate.
174    pub estimate_type: EstimateType,
175    /// Human-readable description (e.g. "ECL allowance — trade receivables").
176    pub description: String,
177    /// Management's point estimate (the amount recognised in the financial statements).
178    #[serde(with = "crate::serde_decimal")]
179    pub management_point_estimate: Decimal,
180    /// Auditor's independent point estimate (when developed as an audit procedure).
181    #[serde(
182        default,
183        skip_serializing_if = "Option::is_none",
184        with = "crate::serde_decimal::option"
185    )]
186    pub auditor_point_estimate: Option<Decimal>,
187    /// Auditor's assessment of estimation uncertainty per ISA 540.
188    pub estimation_uncertainty: UncertaintyLevel,
189    /// Auditor's assessment of the complexity of the estimation process.
190    pub complexity: EstimateComplexity,
191    /// Key assumptions identified and assessed during the audit.
192    pub assumptions: Vec<EstimateAssumption>,
193    /// Retrospective review of the prior-period estimate (when applicable).
194    pub retrospective_review: Option<RetrospectiveReview>,
195    /// Consolidated ISA 540 risk factor indicators for the estimate.
196    pub isa540_risk_factors: Isa540RiskFactors,
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use rust_decimal_macros::dec;
203
204    #[test]
205    fn test_estimate_type_display() {
206        assert_eq!(
207            EstimateType::ExpectedCreditLoss.to_string(),
208            "Expected Credit Loss"
209        );
210        assert_eq!(
211            EstimateType::PensionObligation.to_string(),
212            "Pension Obligation"
213        );
214    }
215
216    #[test]
217    fn test_accounting_estimate_roundtrip() {
218        let estimate = AccountingEstimate {
219            id: "EST-001".to_string(),
220            entity_code: "C001".to_string(),
221            estimate_type: EstimateType::ExpectedCreditLoss,
222            description: "ECL allowance — trade receivables".to_string(),
223            management_point_estimate: dec!(125000.00),
224            auditor_point_estimate: Some(dec!(130000.00)),
225            estimation_uncertainty: UncertaintyLevel::High,
226            complexity: EstimateComplexity::Moderate,
227            assumptions: vec![EstimateAssumption {
228                description: "12-month default rate 2.5%".to_string(),
229                sensitivity: dec!(50000.00),
230                reasonableness: AssumptionAssessment::Reasonable,
231            }],
232            retrospective_review: Some(RetrospectiveReview {
233                prior_period_estimate: dec!(115000.00),
234                actual_outcome: dec!(118000.00),
235                variance: dec!(3000.00),
236                variance_percentage: dec!(2.61),
237                management_bias_indicator: false,
238            }),
239            isa540_risk_factors: Isa540RiskFactors {
240                estimation_uncertainty: UncertaintyLevel::High,
241                complexity: EstimateComplexity::Moderate,
242                subjectivity: SubjectivityLevel::Medium,
243            },
244        };
245
246        let json = serde_json::to_string(&estimate).unwrap();
247        let parsed: AccountingEstimate = serde_json::from_str(&json).unwrap();
248        assert_eq!(parsed.entity_code, "C001");
249        assert_eq!(parsed.estimate_type, EstimateType::ExpectedCreditLoss);
250        assert!(parsed.auditor_point_estimate.is_some());
251    }
252}