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}