Skip to main content

datasynth_core/models/audit/
going_concern.rs

1//! Going concern assessment models — ISA 570 / ASC 205-40.
2//!
3//! Going concern is a fundamental assumption underlying the preparation of
4//! financial statements.  Auditors are required (ISA 570) to evaluate whether
5//! there is a material uncertainty about the entity's ability to continue as a
6//! going concern over the next twelve months.
7
8use chrono::NaiveDate;
9use rust_decimal::Decimal;
10use serde::{Deserialize, Serialize};
11
12// ---------------------------------------------------------------------------
13// Enums
14// ---------------------------------------------------------------------------
15
16/// Type of going concern indicator identified during the assessment.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum GoingConcernIndicatorType {
20    /// Entity has sustained operating losses over multiple periods.
21    RecurringOperatingLosses,
22    /// Net cash outflows from operations over one or more periods.
23    NegativeOperatingCashFlow,
24    /// Current liabilities exceed current assets (negative working capital).
25    WorkingCapitalDeficiency,
26    /// One or more financial covenants have been breached.
27    DebtCovenantBreach,
28    /// Departure of a major customer materially impacting revenue.
29    LossOfKeyCustomer,
30    /// Regulatory action threatening the entity's licence to operate.
31    RegulatoryAction,
32    /// Material litigation exposure that could threaten solvency.
33    LitigationExposure,
34    /// Entity has been unable to refinance or obtain new credit facilities.
35    InabilityToObtainFinancing,
36}
37
38impl std::fmt::Display for GoingConcernIndicatorType {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        let s = match self {
41            Self::RecurringOperatingLosses => "Recurring Operating Losses",
42            Self::NegativeOperatingCashFlow => "Negative Operating Cash Flow",
43            Self::WorkingCapitalDeficiency => "Working Capital Deficiency",
44            Self::DebtCovenantBreach => "Debt Covenant Breach",
45            Self::LossOfKeyCustomer => "Loss of Key Customer",
46            Self::RegulatoryAction => "Regulatory Action",
47            Self::LitigationExposure => "Litigation Exposure",
48            Self::InabilityToObtainFinancing => "Inability to Obtain Financing",
49        };
50        write!(f, "{s}")
51    }
52}
53
54/// Severity classification of a going concern indicator.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "snake_case")]
57pub enum GoingConcernSeverity {
58    /// Indicator present but manageable; unlikely to threaten continuity alone.
59    Low,
60    /// Indicator creates meaningful doubt; mitigating plans are required.
61    Medium,
62    /// Indicator poses a serious threat to the entity's continuity.
63    High,
64}
65
66/// Auditor's overall conclusion on going concern.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum GoingConcernConclusion {
70    /// No material uncertainty exists; going concern basis is appropriate.
71    #[default]
72    NoMaterialUncertainty,
73    /// A material uncertainty exists and is adequately disclosed (ISA 570.22).
74    MaterialUncertaintyExists,
75    /// Significant doubt that the entity is a going concern (ASC 205-40).
76    GoingConcernDoubt,
77}
78
79// ---------------------------------------------------------------------------
80// Indicator
81// ---------------------------------------------------------------------------
82
83/// A single going concern indicator identified during the assessment.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct GoingConcernIndicator {
86    /// Nature of the indicator.
87    pub indicator_type: GoingConcernIndicatorType,
88    /// Assessed severity.
89    pub severity: GoingConcernSeverity,
90    /// Narrative description of the specific circumstances.
91    pub description: String,
92    /// Quantitative measure associated with the indicator (e.g. net loss amount).
93    #[serde(
94        with = "rust_decimal::serde::str_option",
95        skip_serializing_if = "Option::is_none",
96        default
97    )]
98    pub quantitative_measure: Option<Decimal>,
99    /// Threshold at which the indicator becomes critical (e.g. covenant limit).
100    #[serde(
101        with = "rust_decimal::serde::str_option",
102        skip_serializing_if = "Option::is_none",
103        default
104    )]
105    pub threshold: Option<Decimal>,
106}
107
108// ---------------------------------------------------------------------------
109// Assessment
110// ---------------------------------------------------------------------------
111
112/// Going concern assessment prepared for a single entity and reporting period.
113///
114/// One assessment is generated per entity per period.  The auditor's
115/// conclusion is driven by the number and severity of indicators identified.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct GoingConcernAssessment {
118    /// Entity code of the assessed entity.
119    pub entity_code: String,
120    /// Date on which the assessment was finalised.
121    pub assessment_date: NaiveDate,
122    /// Human-readable period descriptor (e.g. "FY2024").
123    pub assessment_period: String,
124    /// Indicators identified during the assessment (may be empty).
125    pub indicators: Vec<GoingConcernIndicator>,
126    /// Management's plans to address the identified indicators.
127    pub management_plans: Vec<String>,
128    /// Auditor's overall conclusion.
129    pub auditor_conclusion: GoingConcernConclusion,
130    /// Whether a material uncertainty paragraph is required in the audit report.
131    pub material_uncertainty_exists: bool,
132}
133
134impl GoingConcernAssessment {
135    /// Derive the conclusion and `material_uncertainty_exists` flag from the
136    /// number of indicators present.
137    ///
138    /// | Indicator count | Conclusion                  | Material uncertainty |
139    /// |-----------------|-----------------------------|----------------------|
140    /// | 0               | `NoMaterialUncertainty`     | false                |
141    /// | 1–2             | `MaterialUncertaintyExists` | true                 |
142    /// | 3+              | `GoingConcernDoubt`         | true                 |
143    pub fn conclude_from_indicators(mut self) -> Self {
144        let n = self.indicators.len();
145        self.auditor_conclusion = match n {
146            0 => GoingConcernConclusion::NoMaterialUncertainty,
147            1..=2 => GoingConcernConclusion::MaterialUncertaintyExists,
148            _ => GoingConcernConclusion::GoingConcernDoubt,
149        };
150        self.material_uncertainty_exists = n > 0;
151        self
152    }
153}