Skip to main content

datasynth_core/models/
provision.rs

1//! Provisions and contingencies models — IAS 37 / ASC 450.
2//!
3//! This module provides data models for provisions (recognised liabilities of
4//! uncertain timing or amount), contingent liabilities (disclosed but not
5//! recognised), and provision movement roll-forwards.
6//!
7//! # Framework differences
8//!
9//! | Criterion          | IAS 37 (IFRS)              | ASC 450 (US GAAP)          |
10//! |--------------------|----------------------------|----------------------------|
11//! | Recognition        | Probable (>50%)            | Probable (>75%)            |
12//! | Measurement        | Best estimate              | Lower end of range          |
13//! | Discounting        | Required when material     | Permitted                   |
14//! | Contingent liab.   | Possible — disclose only   | Possible — disclose only    |
15//! | Remote             | No disclosure required     | No disclosure required      |
16
17use chrono::NaiveDate;
18use rust_decimal::Decimal;
19use serde::{Deserialize, Serialize};
20
21// ---------------------------------------------------------------------------
22// Enums
23// ---------------------------------------------------------------------------
24
25/// Category of provision.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum ProvisionType {
29    /// Product or service warranty obligations.
30    Warranty,
31    /// Restructuring costs (redundancy, lease termination, onerous obligations).
32    Restructuring,
33    /// Pending or threatened litigation claims.
34    LegalClaim,
35    /// Environmental clean-up or remediation obligations.
36    EnvironmentalRemediation,
37    /// Contracts where unavoidable costs exceed expected economic benefits.
38    OnerousContract,
39    /// Asset retirement / decommissioning obligations.
40    Decommissioning,
41}
42
43impl std::fmt::Display for ProvisionType {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let s = match self {
46            Self::Warranty => "Warranty",
47            Self::Restructuring => "Restructuring",
48            Self::LegalClaim => "Legal Claim",
49            Self::EnvironmentalRemediation => "Environmental Remediation",
50            Self::OnerousContract => "Onerous Contract",
51            Self::Decommissioning => "Decommissioning",
52        };
53        write!(f, "{s}")
54    }
55}
56
57/// Probability level for contingent items.
58///
59/// Drives both recognition (provisions) and disclosure (contingent liabilities).
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum ContingentProbability {
63    /// Remote — no disclosure required (<10%).
64    Remote,
65    /// Possible — disclose but do not recognise (10%–50% under IFRS; 10%–75% under US GAAP).
66    Possible,
67    /// Probable — recognise as provision (>50% under IFRS; >75% under US GAAP).
68    Probable,
69}
70
71// ---------------------------------------------------------------------------
72// Provision
73// ---------------------------------------------------------------------------
74
75/// A recognised provision per IAS 37 / ASC 450.
76///
77/// A provision is recognised when there is a present obligation, an outflow of
78/// resources is probable, and a reliable estimate can be made.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct Provision {
81    /// Unique provision identifier.
82    pub id: String,
83    /// Company / entity code.
84    pub entity_code: String,
85    /// Provision category.
86    pub provision_type: ProvisionType,
87    /// Description of the obligation (e.g. "Product warranty — FY2024 sales").
88    pub description: String,
89    /// Best estimate of the expenditure required to settle the obligation.
90    #[serde(with = "rust_decimal::serde::str")]
91    pub best_estimate: Decimal,
92    /// Lower end of the estimated range.
93    #[serde(with = "rust_decimal::serde::str")]
94    pub range_low: Decimal,
95    /// Upper end of the estimated range.
96    #[serde(with = "rust_decimal::serde::str")]
97    pub range_high: Decimal,
98    /// Discount rate applied to long-term provisions (e.g. 0.04 = 4%).
99    /// `None` for provisions expected to be settled within 12 months.
100    #[serde(
101        with = "rust_decimal::serde::str_option",
102        skip_serializing_if = "Option::is_none",
103        default
104    )]
105    pub discount_rate: Option<Decimal>,
106    /// Expected date of cash outflow / settlement.
107    pub expected_utilization_date: NaiveDate,
108    /// Accounting framework governing recognition: `"IFRS"` or `"US_GAAP"`.
109    pub framework: String,
110    /// Reporting currency code.
111    pub currency: String,
112}
113
114// ---------------------------------------------------------------------------
115// Contingent Liability
116// ---------------------------------------------------------------------------
117
118/// A contingent liability disclosed in the notes per IAS 37.86 / ASC 450-20-50.
119///
120/// Contingent liabilities are **not** recognised on the balance sheet but are
121/// disclosed when the probability is Possible (or Probable with uncertain amount).
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ContingentLiability {
124    /// Unique identifier.
125    pub id: String,
126    /// Company / entity code.
127    pub entity_code: String,
128    /// Nature of the contingency (e.g. "Pending patent infringement lawsuit").
129    pub nature: String,
130    /// Assessed probability level.
131    pub probability: ContingentProbability,
132    /// Best estimate of the potential exposure (if determinable).
133    #[serde(
134        with = "rust_decimal::serde::str_option",
135        skip_serializing_if = "Option::is_none",
136        default
137    )]
138    pub estimated_amount: Option<Decimal>,
139    /// Whether this item requires disclosure only (true) or could be recognised (false).
140    ///
141    /// Always `true` for Possible items; `false` would indicate the entity is
142    /// still evaluating whether recognition criteria are met.
143    pub disclosure_only: bool,
144    /// Reporting currency code.
145    pub currency: String,
146}
147
148// ---------------------------------------------------------------------------
149// Provision Movement
150// ---------------------------------------------------------------------------
151
152/// Roll-forward of a provision balance for one reporting period.
153///
154/// Identity: `opening + additions − utilizations − reversals + unwinding_of_discount = closing`
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ProvisionMovement {
157    /// Reference to the parent `Provision.id`.
158    pub provision_id: String,
159    /// Period label (e.g. "2024-Q4" or "FY2024").
160    pub period: String,
161    /// Provision balance at start of period.
162    #[serde(with = "rust_decimal::serde::str")]
163    pub opening: Decimal,
164    /// New provisions recognised during the period.
165    #[serde(with = "rust_decimal::serde::str")]
166    pub additions: Decimal,
167    /// Amounts utilised (actual cash payments) during the period.
168    #[serde(with = "rust_decimal::serde::str")]
169    pub utilizations: Decimal,
170    /// Provisions reversed (no longer required) during the period.
171    #[serde(with = "rust_decimal::serde::str")]
172    pub reversals: Decimal,
173    /// Unwinding of discount on long-term provisions (finance cost).
174    #[serde(with = "rust_decimal::serde::str")]
175    pub unwinding_of_discount: Decimal,
176    /// Provision balance at end of period.
177    /// `opening + additions − utilizations − reversals + unwinding_of_discount`
178    #[serde(with = "rust_decimal::serde::str")]
179    pub closing: Decimal,
180}