1use chrono::NaiveDate;
12use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14use uuid::Uuid;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct AnalyticalProcedure {
19 pub procedure_id: Uuid,
21
22 pub engagement_id: Uuid,
24
25 pub account_area: String,
27
28 pub procedure_type: AnalyticalProcedureType,
30
31 pub purpose: AnalyticalPurpose,
33
34 pub expectation: AnalyticalExpectation,
36
37 #[serde(with = "rust_decimal::serde::str")]
39 pub actual_value: Decimal,
40
41 #[serde(with = "rust_decimal::serde::str")]
43 pub variance: Decimal,
44
45 #[serde(with = "rust_decimal::serde::str")]
47 pub variance_percent: Decimal,
48
49 #[serde(with = "rust_decimal::serde::str")]
51 pub investigation_threshold: Decimal,
52
53 pub exceeds_threshold: bool,
55
56 pub investigation: Option<VarianceInvestigation>,
58
59 pub conclusion: AnalyticalConclusion,
61
62 pub procedure_date: NaiveDate,
64
65 pub prepared_by: String,
67
68 pub reviewed_by: Option<String>,
70
71 pub workpaper_reference: Option<String>,
73}
74
75impl AnalyticalProcedure {
76 pub fn new(
78 engagement_id: Uuid,
79 account_area: impl Into<String>,
80 procedure_type: AnalyticalProcedureType,
81 purpose: AnalyticalPurpose,
82 ) -> Self {
83 Self {
84 procedure_id: Uuid::now_v7(),
85 engagement_id,
86 account_area: account_area.into(),
87 procedure_type,
88 purpose,
89 expectation: AnalyticalExpectation::default(),
90 actual_value: Decimal::ZERO,
91 variance: Decimal::ZERO,
92 variance_percent: Decimal::ZERO,
93 investigation_threshold: Decimal::ZERO,
94 exceeds_threshold: false,
95 investigation: None,
96 conclusion: AnalyticalConclusion::NotCompleted,
97 procedure_date: chrono::Utc::now().date_naive(),
98 prepared_by: String::new(),
99 reviewed_by: None,
100 workpaper_reference: None,
101 }
102 }
103
104 pub fn calculate_variance(&mut self) {
106 self.variance = self.actual_value - self.expectation.expected_value;
107
108 if self.expectation.expected_value != Decimal::ZERO {
110 self.variance_percent =
111 (self.variance / self.expectation.expected_value) * Decimal::from(100);
112 } else {
113 self.variance_percent = Decimal::ZERO;
114 }
115
116 self.exceeds_threshold = self.variance.abs() > self.investigation_threshold;
118 }
119
120 pub fn requires_investigation(&self) -> bool {
122 self.exceeds_threshold
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
128#[serde(rename_all = "snake_case")]
129pub enum AnalyticalProcedureType {
130 #[default]
132 Trend,
133 Ratio,
135 Reasonableness,
137 Regression,
139 BudgetComparison,
141 IndustryComparison,
143 NonFinancialRelationship,
145}
146
147impl std::fmt::Display for AnalyticalProcedureType {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self {
150 Self::Trend => write!(f, "Trend Analysis"),
151 Self::Ratio => write!(f, "Ratio Analysis"),
152 Self::Reasonableness => write!(f, "Reasonableness Test"),
153 Self::Regression => write!(f, "Regression Analysis"),
154 Self::BudgetComparison => write!(f, "Budget Comparison"),
155 Self::IndustryComparison => write!(f, "Industry Comparison"),
156 Self::NonFinancialRelationship => write!(f, "Non-Financial Relationship"),
157 }
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
163#[serde(rename_all = "snake_case")]
164pub enum AnalyticalPurpose {
165 #[default]
167 RiskAssessment,
168 Substantive,
170 FinalReview,
172}
173
174impl std::fmt::Display for AnalyticalPurpose {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 match self {
177 Self::RiskAssessment => write!(f, "Risk Assessment"),
178 Self::Substantive => write!(f, "Substantive"),
179 Self::FinalReview => write!(f, "Final Review"),
180 }
181 }
182}
183
184#[derive(Debug, Clone, Default, Serialize, Deserialize)]
186pub struct AnalyticalExpectation {
187 #[serde(with = "rust_decimal::serde::str")]
189 pub expected_value: Decimal,
190
191 pub expectation_basis: ExpectationBasis,
193
194 pub methodology: String,
196
197 pub data_reliability: ReliabilityLevel,
199
200 pub precision_level: PrecisionLevel,
202
203 pub key_assumptions: Vec<String>,
205
206 pub data_sources: Vec<String>,
208}
209
210impl AnalyticalExpectation {
211 pub fn new(expected_value: Decimal, expectation_basis: ExpectationBasis) -> Self {
213 Self {
214 expected_value,
215 expectation_basis,
216 methodology: String::new(),
217 data_reliability: ReliabilityLevel::default(),
218 precision_level: PrecisionLevel::default(),
219 key_assumptions: Vec::new(),
220 data_sources: Vec::new(),
221 }
222 }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
227#[serde(rename_all = "snake_case")]
228pub enum ExpectationBasis {
229 #[default]
231 PriorPeriod,
232 Budget,
234 Industry,
236 NonFinancial,
238 StatisticalModel,
240 IndependentCalculation,
242 InterimResults,
244}
245
246impl std::fmt::Display for ExpectationBasis {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 match self {
249 Self::PriorPeriod => write!(f, "Prior Period"),
250 Self::Budget => write!(f, "Budget/Forecast"),
251 Self::Industry => write!(f, "Industry Data"),
252 Self::NonFinancial => write!(f, "Non-Financial Data"),
253 Self::StatisticalModel => write!(f, "Statistical Model"),
254 Self::IndependentCalculation => write!(f, "Independent Calculation"),
255 Self::InterimResults => write!(f, "Interim Results"),
256 }
257 }
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
262#[serde(rename_all = "snake_case")]
263pub enum ReliabilityLevel {
264 Low,
266 #[default]
268 Moderate,
269 High,
271}
272
273impl std::fmt::Display for ReliabilityLevel {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 match self {
276 Self::Low => write!(f, "Low"),
277 Self::Moderate => write!(f, "Moderate"),
278 Self::High => write!(f, "High"),
279 }
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
285#[serde(rename_all = "snake_case")]
286pub enum PrecisionLevel {
287 Low,
289 #[default]
291 Moderate,
292 High,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct VarianceInvestigation {
299 pub investigation_id: Uuid,
301
302 pub explanation: String,
304
305 pub explanation_source: String,
307
308 pub corroborated: bool,
310
311 pub corroborating_evidence: Vec<String>,
313
314 pub additional_procedures: Vec<String>,
316
317 pub variance_explained: bool,
319
320 #[serde(default, with = "rust_decimal::serde::str_option")]
322 pub misstatement_amount: Option<Decimal>,
323
324 pub conclusion: InvestigationConclusion,
326}
327
328impl VarianceInvestigation {
329 pub fn new() -> Self {
331 Self {
332 investigation_id: Uuid::now_v7(),
333 explanation: String::new(),
334 explanation_source: String::new(),
335 corroborated: false,
336 corroborating_evidence: Vec::new(),
337 additional_procedures: Vec::new(),
338 variance_explained: false,
339 misstatement_amount: None,
340 conclusion: InvestigationConclusion::NotCompleted,
341 }
342 }
343}
344
345impl Default for VarianceInvestigation {
346 fn default() -> Self {
347 Self::new()
348 }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
353#[serde(rename_all = "snake_case")]
354pub enum InvestigationConclusion {
355 #[default]
357 NotCompleted,
358 Explained,
360 PotentialMisstatement,
362 MisstatementIdentified,
364 UnableToExplain,
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
370#[serde(rename_all = "snake_case")]
371pub enum AnalyticalConclusion {
372 #[default]
374 NotCompleted,
375 Consistent,
377 InvestigatedAndExplained,
379 PotentialMisstatement,
381 MisstatementIdentified,
383 Inconclusive,
385}
386
387impl std::fmt::Display for AnalyticalConclusion {
388 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389 match self {
390 Self::NotCompleted => write!(f, "Not Completed"),
391 Self::Consistent => write!(f, "Consistent with Expectations"),
392 Self::InvestigatedAndExplained => write!(f, "Investigated and Explained"),
393 Self::PotentialMisstatement => write!(f, "Potential Misstatement"),
394 Self::MisstatementIdentified => write!(f, "Misstatement Identified"),
395 Self::Inconclusive => write!(f, "Inconclusive"),
396 }
397 }
398}
399
400#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
402#[serde(rename_all = "snake_case")]
403pub enum FinancialRatio {
404 GrossMargin,
406 OperatingMargin,
407 NetProfitMargin,
408 ReturnOnAssets,
409 ReturnOnEquity,
410
411 CurrentRatio,
413 QuickRatio,
414 CashRatio,
415
416 InventoryTurnover,
418 ReceivablesTurnover,
419 PayablesTurnover,
420 AssetTurnover,
421 DaysSalesOutstanding,
422 DaysPayablesOutstanding,
423 DaysInventoryOnHand,
424
425 DebtToEquity,
427 DebtToAssets,
428 InterestCoverage,
429
430 Custom,
432}
433
434impl std::fmt::Display for FinancialRatio {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 match self {
437 Self::GrossMargin => write!(f, "Gross Margin"),
438 Self::OperatingMargin => write!(f, "Operating Margin"),
439 Self::NetProfitMargin => write!(f, "Net Profit Margin"),
440 Self::ReturnOnAssets => write!(f, "Return on Assets"),
441 Self::ReturnOnEquity => write!(f, "Return on Equity"),
442 Self::CurrentRatio => write!(f, "Current Ratio"),
443 Self::QuickRatio => write!(f, "Quick Ratio"),
444 Self::CashRatio => write!(f, "Cash Ratio"),
445 Self::InventoryTurnover => write!(f, "Inventory Turnover"),
446 Self::ReceivablesTurnover => write!(f, "Receivables Turnover"),
447 Self::PayablesTurnover => write!(f, "Payables Turnover"),
448 Self::AssetTurnover => write!(f, "Asset Turnover"),
449 Self::DaysSalesOutstanding => write!(f, "Days Sales Outstanding"),
450 Self::DaysPayablesOutstanding => write!(f, "Days Payables Outstanding"),
451 Self::DaysInventoryOnHand => write!(f, "Days Inventory on Hand"),
452 Self::DebtToEquity => write!(f, "Debt to Equity"),
453 Self::DebtToAssets => write!(f, "Debt to Assets"),
454 Self::InterestCoverage => write!(f, "Interest Coverage"),
455 Self::Custom => write!(f, "Custom Ratio"),
456 }
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463 use rust_decimal_macros::dec;
464
465 #[test]
466 fn test_analytical_procedure_creation() {
467 let procedure = AnalyticalProcedure::new(
468 Uuid::now_v7(),
469 "Revenue",
470 AnalyticalProcedureType::Trend,
471 AnalyticalPurpose::Substantive,
472 );
473
474 assert_eq!(procedure.account_area, "Revenue");
475 assert_eq!(procedure.procedure_type, AnalyticalProcedureType::Trend);
476 assert_eq!(procedure.conclusion, AnalyticalConclusion::NotCompleted);
477 }
478
479 #[test]
480 fn test_variance_calculation() {
481 let mut procedure = AnalyticalProcedure::new(
482 Uuid::now_v7(),
483 "Revenue",
484 AnalyticalProcedureType::Trend,
485 AnalyticalPurpose::Substantive,
486 );
487
488 procedure.expectation =
489 AnalyticalExpectation::new(dec!(100000), ExpectationBasis::PriorPeriod);
490 procedure.actual_value = dec!(110000);
491 procedure.investigation_threshold = dec!(5000);
492
493 procedure.calculate_variance();
494
495 assert_eq!(procedure.variance, dec!(10000));
496 assert_eq!(procedure.variance_percent, dec!(10));
497 assert!(procedure.exceeds_threshold);
498 }
499
500 #[test]
501 fn test_variance_within_threshold() {
502 let mut procedure = AnalyticalProcedure::new(
503 Uuid::now_v7(),
504 "Cost of Sales",
505 AnalyticalProcedureType::Reasonableness,
506 AnalyticalPurpose::FinalReview,
507 );
508
509 procedure.expectation =
510 AnalyticalExpectation::new(dec!(50000), ExpectationBasis::IndependentCalculation);
511 procedure.actual_value = dec!(51000);
512 procedure.investigation_threshold = dec!(2500);
513
514 procedure.calculate_variance();
515
516 assert_eq!(procedure.variance, dec!(1000));
517 assert!(!procedure.exceeds_threshold);
518 }
519}