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}