Skip to main content

datasynth_core/models/audit/
scots.rs

1//! Significant Classes of Transactions (SCOTS) per ISA 315 (Revised 2019).
2//!
3//! ISA 315.26 requires the auditor to identify and understand the significant
4//! classes of transactions (SCOTs), account balances and disclosures.  SCOTs
5//! drive the design of the auditor's information-technology and internal-control
6//! understanding and the nature, timing, and extent of further audit procedures.
7//!
8//! Each SCOT is characterised by:
9//! - Its business process (O2C, P2P, R2R, H2R)
10//! - Transaction type (routine, non-routine, estimation)
11//! - Processing method (fully automated, semi-automated, manual)
12//! - A critical path of four stages (Initiation → Recording → Processing → Reporting)
13//! - Relevant financial statement assertions (from the CRA model)
14//! - For estimation SCOTs: an estimation complexity rating per ISA 540
15//!
16//! References:
17//! - ISA 315 (Revised 2019) §26 — Significant classes of transactions
18//! - ISA 330 §6 — Further audit procedures in response to SCOT assessment
19//! - ISA 540 — Accounting estimates (drives estimation complexity)
20
21use rust_decimal::Decimal;
22use serde::{Deserialize, Serialize};
23
24// ---------------------------------------------------------------------------
25// Enumerations
26// ---------------------------------------------------------------------------
27
28/// Significance level of a class of transactions for the audit.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum ScotSignificance {
32    /// Material individually or in aggregate; requires the most extensive procedures.
33    High,
34    /// Significant but not individually material; moderate extent of procedures.
35    Medium,
36    /// Below materiality; limited procedures or analytical only.
37    Low,
38}
39
40impl std::fmt::Display for ScotSignificance {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        let s = match self {
43            Self::High => "High",
44            Self::Medium => "Medium",
45            Self::Low => "Low",
46        };
47        write!(f, "{s}")
48    }
49}
50
51/// Type of transaction within a SCOT.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub enum ScotTransactionType {
55    /// High-volume, standardised, recurring transactions with consistent processing.
56    Routine,
57    /// Infrequent or unusual transactions requiring significant judgment or
58    /// management approval (e.g. asset disposals, significant contracts).
59    NonRoutine,
60    /// Accounting estimates with inherent measurement uncertainty (ISA 540).
61    Estimation,
62}
63
64impl std::fmt::Display for ScotTransactionType {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        let s = match self {
67            Self::Routine => "Routine",
68            Self::NonRoutine => "Non-Routine",
69            Self::Estimation => "Estimation",
70        };
71        write!(f, "{s}")
72    }
73}
74
75/// How transactions within the SCOT are processed.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum ProcessingMethod {
79    /// System-initiated and system-posted — no manual intervention in normal processing.
80    FullyAutomated,
81    /// System-initiated but requires manual approval or manual journal entry
82    /// for certain steps (e.g. three-way match exception handling).
83    SemiAutomated,
84    /// Primarily manual processing — spreadsheet-based, clerk-prepared, etc.
85    Manual,
86}
87
88impl std::fmt::Display for ProcessingMethod {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        let s = match self {
91            Self::FullyAutomated => "Fully Automated",
92            Self::SemiAutomated => "Semi-Automated",
93            Self::Manual => "Manual",
94        };
95        write!(f, "{s}")
96    }
97}
98
99/// Complexity of the underlying estimate per ISA 540.
100///
101/// Only populated for `ScotTransactionType::Estimation` SCOTs.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub enum EstimationComplexity {
105    /// Straightforward estimation with observable inputs (e.g. straight-line depreciation).
106    Simple,
107    /// Moderate complexity — some unobservable inputs or model uncertainty
108    /// (e.g. ECL provisioning with internal historical data).
109    Moderate,
110    /// Highly complex — significant unobservable inputs, multiple methodologies possible,
111    /// or high sensitivity to assumptions (e.g. pension obligations, level-3 fair value).
112    Complex,
113}
114
115impl std::fmt::Display for EstimationComplexity {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        let s = match self {
118            Self::Simple => "Simple",
119            Self::Moderate => "Moderate",
120            Self::Complex => "Complex",
121        };
122        write!(f, "{s}")
123    }
124}
125
126// ---------------------------------------------------------------------------
127// Critical path
128// ---------------------------------------------------------------------------
129
130/// A single stage in the SCOT's transaction processing critical path.
131///
132/// The standard four stages are:
133/// 1. Initiation — how transactions are initiated (automated trigger or manual request)
134/// 2. Recording — how the transaction is recorded in source documents / systems
135/// 3. Processing — system processing, matching, posting to the GL
136/// 4. Reporting — how the transaction flows into the financial statements
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct CriticalPathStage {
139    /// Stage name (e.g. "Initiation", "Recording", "Processing", "Reporting").
140    pub stage_name: String,
141    /// Brief description of how this stage operates for this SCOT.
142    pub description: String,
143    /// Whether this stage is fully automated (system-driven, no manual input).
144    pub is_automated: bool,
145    /// ID of the key internal control operating at this stage, if any.
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub key_control_id: Option<String>,
148}
149
150// ---------------------------------------------------------------------------
151// Main SCOT struct
152// ---------------------------------------------------------------------------
153
154/// A Significant Class of Transactions (SCOT) per ISA 315.
155///
156/// One SCOT is generated per major business process / transaction class.
157/// SCOTs drive the scope of the auditor's control and substantive testing.
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct SignificantClassOfTransactions {
160    /// Unique identifier for this SCOT (deterministic slug).
161    pub id: String,
162    /// Entity / company code.
163    pub entity_code: String,
164    /// Descriptive name (e.g. "Revenue — Product Sales", "Purchases — Raw Materials").
165    pub scot_name: String,
166    /// Business process code driving this class (O2C, P2P, R2R, H2R, etc.).
167    pub business_process: String,
168    /// Significance of this SCOT for the audit.
169    pub significance_level: ScotSignificance,
170    /// Whether the transactions are routine, non-routine, or estimation-based.
171    pub transaction_type: ScotTransactionType,
172    /// Primary processing method for this class of transactions.
173    pub processing_method: ProcessingMethod,
174    /// Approximate number of transactions in the period.
175    pub volume: usize,
176    /// Aggregate monetary value of transactions in the period.
177    #[serde(with = "rust_decimal::serde::str")]
178    pub monetary_value: Decimal,
179    /// The four-stage critical path (Initiation → Recording → Processing → Reporting).
180    pub critical_path: Vec<CriticalPathStage>,
181    /// Financial statement assertions relevant to this SCOT (links to CRA assertions).
182    pub relevant_assertions: Vec<String>,
183    /// GL account areas affected by this SCOT.
184    pub related_account_areas: Vec<String>,
185    /// Estimation complexity — only set for `ScotTransactionType::Estimation` SCOTs.
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub estimation_complexity: Option<EstimationComplexity>,
188}
189
190// ---------------------------------------------------------------------------
191// Tests
192// ---------------------------------------------------------------------------
193
194#[cfg(test)]
195#[allow(clippy::unwrap_used)]
196mod tests {
197    use super::*;
198    use rust_decimal_macros::dec;
199
200    #[test]
201    fn scot_display_impls() {
202        assert_eq!(ScotSignificance::High.to_string(), "High");
203        assert_eq!(ScotTransactionType::Estimation.to_string(), "Estimation");
204        assert_eq!(
205            ProcessingMethod::FullyAutomated.to_string(),
206            "Fully Automated"
207        );
208        assert_eq!(EstimationComplexity::Complex.to_string(), "Complex");
209    }
210
211    #[test]
212    fn scot_structure() {
213        let scot = SignificantClassOfTransactions {
214            id: "SCOT-C001-REVENUE_PRODUCT_SALES".into(),
215            entity_code: "C001".into(),
216            scot_name: "Revenue — Product Sales".into(),
217            business_process: "O2C".into(),
218            significance_level: ScotSignificance::High,
219            transaction_type: ScotTransactionType::Routine,
220            processing_method: ProcessingMethod::SemiAutomated,
221            volume: 5_000,
222            monetary_value: dec!(10_000_000),
223            critical_path: vec![
224                CriticalPathStage {
225                    stage_name: "Initiation".into(),
226                    description: "Sales order created by customer / sales team".into(),
227                    is_automated: false,
228                    key_control_id: Some("C001".into()),
229                },
230                CriticalPathStage {
231                    stage_name: "Recording".into(),
232                    description: "System records SO upon credit approval".into(),
233                    is_automated: true,
234                    key_control_id: None,
235                },
236            ],
237            relevant_assertions: vec!["Occurrence".into(), "Accuracy".into()],
238            related_account_areas: vec!["Revenue".into(), "Trade Receivables".into()],
239            estimation_complexity: None,
240        };
241
242        assert_eq!(scot.critical_path.len(), 2);
243        assert!(scot.estimation_complexity.is_none());
244        assert_eq!(scot.significance_level, ScotSignificance::High);
245    }
246
247    #[test]
248    fn estimation_scot_has_complexity() {
249        let scot = SignificantClassOfTransactions {
250            id: "SCOT-C001-ECL_BAD_DEBT".into(),
251            entity_code: "C001".into(),
252            scot_name: "ECL / Bad Debt Provision".into(),
253            business_process: "R2R".into(),
254            significance_level: ScotSignificance::High,
255            transaction_type: ScotTransactionType::Estimation,
256            processing_method: ProcessingMethod::Manual,
257            volume: 12,
258            monetary_value: dec!(250_000),
259            critical_path: Vec::new(),
260            relevant_assertions: vec!["ValuationAndAllocation".into()],
261            related_account_areas: vec!["Trade Receivables".into(), "Provisions".into()],
262            estimation_complexity: Some(EstimationComplexity::Moderate),
263        };
264
265        assert!(scot.estimation_complexity.is_some());
266        assert_eq!(
267            scot.estimation_complexity.unwrap(),
268            EstimationComplexity::Moderate
269        );
270    }
271}