Skip to main content

datasynth_standards/audit/
isa_reference.rs

1//! ISA Standard References (International Standards on Auditing).
2//!
3//! Provides comprehensive ISA standard enumerations and mapping structures
4//! for documenting audit procedure compliance with specific ISA requirements.
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// ISA Standard enumeration covering all major ISA standards.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum IsaStandard {
12    // 200 Series - General Principles and Responsibilities
13    /// ISA 200: Overall Objectives of the Independent Auditor
14    Isa200,
15    /// ISA 210: Agreeing the Terms of Audit Engagements
16    Isa210,
17    /// ISA 220: Quality Management for an Audit of Financial Statements
18    Isa220,
19    /// ISA 230: Audit Documentation
20    Isa230,
21    /// ISA 240: The Auditor's Responsibilities Relating to Fraud
22    Isa240,
23    /// ISA 250: Consideration of Laws and Regulations
24    Isa250,
25    /// ISA 260: Communication with Those Charged with Governance
26    Isa260,
27    /// ISA 265: Communicating Deficiencies in Internal Control
28    Isa265,
29
30    // 300 Series - Risk Assessment and Response
31    /// ISA 300: Planning an Audit of Financial Statements
32    Isa300,
33    /// ISA 315: Identifying and Assessing Risks of Material Misstatement
34    Isa315,
35    /// ISA 320: Materiality in Planning and Performing an Audit
36    Isa320,
37    /// ISA 330: The Auditor's Responses to Assessed Risks
38    Isa330,
39
40    // 400 Series - Internal Control
41    /// ISA 402: Audit Considerations Relating to Service Organizations
42    Isa402,
43    /// ISA 450: Evaluation of Misstatements Identified During the Audit
44    Isa450,
45
46    // 500 Series - Audit Evidence
47    /// ISA 500: Audit Evidence
48    Isa500,
49    /// ISA 501: Audit Evidence - Specific Considerations
50    Isa501,
51    /// ISA 505: External Confirmations
52    Isa505,
53    /// ISA 510: Initial Audit Engagements - Opening Balances
54    Isa510,
55    /// ISA 520: Analytical Procedures
56    Isa520,
57    /// ISA 530: Audit Sampling
58    Isa530,
59    /// ISA 540: Auditing Accounting Estimates and Related Disclosures
60    Isa540,
61    /// ISA 550: Related Parties
62    Isa550,
63    /// ISA 560: Subsequent Events
64    Isa560,
65    /// ISA 570: Going Concern
66    Isa570,
67    /// ISA 580: Written Representations
68    Isa580,
69
70    // 600 Series - Using Work of Others
71    /// ISA 600: Special Considerations - Audits of Group Financial Statements
72    Isa600,
73    /// ISA 610: Using the Work of Internal Auditors
74    Isa610,
75    /// ISA 620: Using the Work of an Auditor's Expert
76    Isa620,
77
78    // 700 Series - Audit Conclusions and Reporting
79    /// ISA 700: Forming an Opinion and Reporting on Financial Statements
80    Isa700,
81    /// ISA 701: Communicating Key Audit Matters
82    Isa701,
83    /// ISA 705: Modifications to the Opinion
84    Isa705,
85    /// ISA 706: Emphasis of Matter and Other Matter Paragraphs
86    Isa706,
87    /// ISA 710: Comparative Information
88    Isa710,
89    /// ISA 720: The Auditor's Responsibilities Relating to Other Information
90    Isa720,
91}
92
93impl IsaStandard {
94    /// Get the standard number as a string (e.g., "315").
95    pub fn number(&self) -> &'static str {
96        match self {
97            Self::Isa200 => "200",
98            Self::Isa210 => "210",
99            Self::Isa220 => "220",
100            Self::Isa230 => "230",
101            Self::Isa240 => "240",
102            Self::Isa250 => "250",
103            Self::Isa260 => "260",
104            Self::Isa265 => "265",
105            Self::Isa300 => "300",
106            Self::Isa315 => "315",
107            Self::Isa320 => "320",
108            Self::Isa330 => "330",
109            Self::Isa402 => "402",
110            Self::Isa450 => "450",
111            Self::Isa500 => "500",
112            Self::Isa501 => "501",
113            Self::Isa505 => "505",
114            Self::Isa510 => "510",
115            Self::Isa520 => "520",
116            Self::Isa530 => "530",
117            Self::Isa540 => "540",
118            Self::Isa550 => "550",
119            Self::Isa560 => "560",
120            Self::Isa570 => "570",
121            Self::Isa580 => "580",
122            Self::Isa600 => "600",
123            Self::Isa610 => "610",
124            Self::Isa620 => "620",
125            Self::Isa700 => "700",
126            Self::Isa701 => "701",
127            Self::Isa705 => "705",
128            Self::Isa706 => "706",
129            Self::Isa710 => "710",
130            Self::Isa720 => "720",
131        }
132    }
133
134    /// Get the full title of the standard.
135    pub fn title(&self) -> &'static str {
136        match self {
137            Self::Isa200 => "Overall Objectives of the Independent Auditor",
138            Self::Isa210 => "Agreeing the Terms of Audit Engagements",
139            Self::Isa220 => "Quality Management for an Audit of Financial Statements",
140            Self::Isa230 => "Audit Documentation",
141            Self::Isa240 => "The Auditor's Responsibilities Relating to Fraud",
142            Self::Isa250 => "Consideration of Laws and Regulations",
143            Self::Isa260 => "Communication with Those Charged with Governance",
144            Self::Isa265 => "Communicating Deficiencies in Internal Control",
145            Self::Isa300 => "Planning an Audit of Financial Statements",
146            Self::Isa315 => "Identifying and Assessing Risks of Material Misstatement",
147            Self::Isa320 => "Materiality in Planning and Performing an Audit",
148            Self::Isa330 => "The Auditor's Responses to Assessed Risks",
149            Self::Isa402 => "Audit Considerations Relating to Service Organizations",
150            Self::Isa450 => "Evaluation of Misstatements Identified During the Audit",
151            Self::Isa500 => "Audit Evidence",
152            Self::Isa501 => "Audit Evidence - Specific Considerations",
153            Self::Isa505 => "External Confirmations",
154            Self::Isa510 => "Initial Audit Engagements - Opening Balances",
155            Self::Isa520 => "Analytical Procedures",
156            Self::Isa530 => "Audit Sampling",
157            Self::Isa540 => "Auditing Accounting Estimates and Related Disclosures",
158            Self::Isa550 => "Related Parties",
159            Self::Isa560 => "Subsequent Events",
160            Self::Isa570 => "Going Concern",
161            Self::Isa580 => "Written Representations",
162            Self::Isa600 => "Special Considerations - Audits of Group Financial Statements",
163            Self::Isa610 => "Using the Work of Internal Auditors",
164            Self::Isa620 => "Using the Work of an Auditor's Expert",
165            Self::Isa700 => "Forming an Opinion and Reporting on Financial Statements",
166            Self::Isa701 => "Communicating Key Audit Matters",
167            Self::Isa705 => "Modifications to the Opinion",
168            Self::Isa706 => "Emphasis of Matter and Other Matter Paragraphs",
169            Self::Isa710 => "Comparative Information",
170            Self::Isa720 => "The Auditor's Responsibilities Relating to Other Information",
171        }
172    }
173
174    /// Get the ISA series this standard belongs to.
175    pub fn series(&self) -> IsaSeries {
176        match self {
177            Self::Isa200
178            | Self::Isa210
179            | Self::Isa220
180            | Self::Isa230
181            | Self::Isa240
182            | Self::Isa250
183            | Self::Isa260
184            | Self::Isa265 => IsaSeries::GeneralPrinciples,
185            Self::Isa300 | Self::Isa315 | Self::Isa320 | Self::Isa330 => IsaSeries::RiskAssessment,
186            Self::Isa402 | Self::Isa450 => IsaSeries::InternalControl,
187            Self::Isa500
188            | Self::Isa501
189            | Self::Isa505
190            | Self::Isa510
191            | Self::Isa520
192            | Self::Isa530
193            | Self::Isa540
194            | Self::Isa550
195            | Self::Isa560
196            | Self::Isa570
197            | Self::Isa580 => IsaSeries::AuditEvidence,
198            Self::Isa600 | Self::Isa610 | Self::Isa620 => IsaSeries::UsingWorkOfOthers,
199            Self::Isa700
200            | Self::Isa701
201            | Self::Isa705
202            | Self::Isa706
203            | Self::Isa710
204            | Self::Isa720 => IsaSeries::Reporting,
205        }
206    }
207
208    /// Returns all ISA standards as a vector.
209    pub fn all() -> Vec<Self> {
210        vec![
211            Self::Isa200,
212            Self::Isa210,
213            Self::Isa220,
214            Self::Isa230,
215            Self::Isa240,
216            Self::Isa250,
217            Self::Isa260,
218            Self::Isa265,
219            Self::Isa300,
220            Self::Isa315,
221            Self::Isa320,
222            Self::Isa330,
223            Self::Isa402,
224            Self::Isa450,
225            Self::Isa500,
226            Self::Isa501,
227            Self::Isa505,
228            Self::Isa510,
229            Self::Isa520,
230            Self::Isa530,
231            Self::Isa540,
232            Self::Isa550,
233            Self::Isa560,
234            Self::Isa570,
235            Self::Isa580,
236            Self::Isa600,
237            Self::Isa610,
238            Self::Isa620,
239            Self::Isa700,
240            Self::Isa701,
241            Self::Isa705,
242            Self::Isa706,
243            Self::Isa710,
244            Self::Isa720,
245        ]
246    }
247}
248
249impl std::fmt::Display for IsaStandard {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        write!(f, "ISA {}", self.number())
252    }
253}
254
255/// ISA Series groupings.
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
257#[serde(rename_all = "snake_case")]
258pub enum IsaSeries {
259    /// 200 Series: General Principles and Responsibilities
260    GeneralPrinciples,
261    /// 300 Series: Risk Assessment and Response
262    RiskAssessment,
263    /// 400 Series: Internal Control
264    InternalControl,
265    /// 500 Series: Audit Evidence
266    AuditEvidence,
267    /// 600 Series: Using Work of Others
268    UsingWorkOfOthers,
269    /// 700 Series: Audit Conclusions and Reporting
270    Reporting,
271}
272
273impl std::fmt::Display for IsaSeries {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        match self {
276            Self::GeneralPrinciples => write!(f, "General Principles and Responsibilities"),
277            Self::RiskAssessment => write!(f, "Risk Assessment and Response"),
278            Self::InternalControl => write!(f, "Internal Control"),
279            Self::AuditEvidence => write!(f, "Audit Evidence"),
280            Self::UsingWorkOfOthers => write!(f, "Using Work of Others"),
281            Self::Reporting => write!(f, "Audit Conclusions and Reporting"),
282        }
283    }
284}
285
286/// Specific ISA requirement reference.
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct IsaRequirement {
289    /// The ISA standard.
290    pub standard: IsaStandard,
291
292    /// Specific paragraph number (e.g., "25" for ISA 315.25).
293    pub paragraph: String,
294
295    /// Type of requirement.
296    pub requirement_type: IsaRequirementType,
297
298    /// Brief description of the requirement.
299    pub description: String,
300
301    /// Whether this is a mandatory ("shall") requirement.
302    pub is_mandatory: bool,
303}
304
305impl IsaRequirement {
306    /// Create a new ISA requirement reference.
307    pub fn new(
308        standard: IsaStandard,
309        paragraph: impl Into<String>,
310        requirement_type: IsaRequirementType,
311        description: impl Into<String>,
312    ) -> Self {
313        let is_mandatory = matches!(requirement_type, IsaRequirementType::Requirement);
314        Self {
315            standard,
316            paragraph: paragraph.into(),
317            requirement_type,
318            description: description.into(),
319            is_mandatory,
320        }
321    }
322
323    /// Get the full reference string (e.g., "ISA 315.25").
324    pub fn reference(&self) -> String {
325        format!("ISA {}.{}", self.standard.number(), self.paragraph)
326    }
327}
328
329/// Type of ISA requirement.
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
331#[serde(rename_all = "snake_case")]
332pub enum IsaRequirementType {
333    /// Objective of the standard.
334    Objective,
335    /// Mandatory requirement ("shall").
336    Requirement,
337    /// Application guidance (non-mandatory).
338    ApplicationGuidance,
339    /// Definition.
340    Definition,
341}
342
343impl std::fmt::Display for IsaRequirementType {
344    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345        match self {
346            Self::Objective => write!(f, "Objective"),
347            Self::Requirement => write!(f, "Requirement"),
348            Self::ApplicationGuidance => write!(f, "Application Guidance"),
349            Self::Definition => write!(f, "Definition"),
350        }
351    }
352}
353
354/// Mapping of audit procedure to ISA requirements.
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct IsaProcedureMapping {
357    /// Unique mapping identifier.
358    pub mapping_id: Uuid,
359
360    /// Audit procedure ID being mapped.
361    pub procedure_id: Uuid,
362
363    /// Procedure description.
364    pub procedure_description: String,
365
366    /// ISA requirements addressed by this procedure.
367    pub isa_requirements: Vec<IsaRequirement>,
368
369    /// Compliance status.
370    pub compliance_status: ComplianceStatus,
371
372    /// Documentation reference.
373    pub documentation_reference: Option<String>,
374
375    /// Notes on compliance.
376    pub compliance_notes: String,
377}
378
379impl IsaProcedureMapping {
380    /// Create a new procedure mapping.
381    pub fn new(procedure_id: Uuid, procedure_description: impl Into<String>) -> Self {
382        Self {
383            mapping_id: Uuid::now_v7(),
384            procedure_id,
385            procedure_description: procedure_description.into(),
386            isa_requirements: Vec::new(),
387            compliance_status: ComplianceStatus::NotAssessed,
388            documentation_reference: None,
389            compliance_notes: String::new(),
390        }
391    }
392
393    /// Add an ISA requirement.
394    pub fn add_requirement(&mut self, requirement: IsaRequirement) {
395        self.isa_requirements.push(requirement);
396    }
397
398    /// Get unique standards covered by this mapping.
399    pub fn standards_covered(&self) -> Vec<IsaStandard> {
400        let mut standards: Vec<_> = self.isa_requirements.iter().map(|r| r.standard).collect();
401        standards.sort_by_key(|s| s.number());
402        standards.dedup();
403        standards
404    }
405}
406
407/// ISA compliance status.
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
409#[serde(rename_all = "snake_case")]
410pub enum ComplianceStatus {
411    /// Not yet assessed.
412    #[default]
413    NotAssessed,
414    /// Fully compliant.
415    Compliant,
416    /// Partially compliant.
417    PartiallyCompliant,
418    /// Non-compliant.
419    NonCompliant,
420    /// Not applicable.
421    NotApplicable,
422}
423
424impl std::fmt::Display for ComplianceStatus {
425    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426        match self {
427            Self::NotAssessed => write!(f, "Not Assessed"),
428            Self::Compliant => write!(f, "Compliant"),
429            Self::PartiallyCompliant => write!(f, "Partially Compliant"),
430            Self::NonCompliant => write!(f, "Non-Compliant"),
431            Self::NotApplicable => write!(f, "Not Applicable"),
432        }
433    }
434}
435
436/// ISA coverage summary for an engagement.
437#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct IsaCoverageSummary {
439    /// Engagement ID.
440    pub engagement_id: Uuid,
441
442    /// Standards explicitly addressed.
443    pub standards_addressed: Vec<IsaStandard>,
444
445    /// Coverage by series.
446    pub coverage_by_series: Vec<SeriesCoverage>,
447
448    /// Overall compliance percentage.
449    pub overall_compliance_percent: f64,
450
451    /// Standards not covered.
452    pub gaps: Vec<IsaStandard>,
453
454    /// Mandatory requirements not addressed.
455    pub unaddressed_mandatory: Vec<IsaRequirement>,
456}
457
458impl IsaCoverageSummary {
459    /// Create a new coverage summary.
460    pub fn new(engagement_id: Uuid) -> Self {
461        Self {
462            engagement_id,
463            standards_addressed: Vec::new(),
464            coverage_by_series: Vec::new(),
465            overall_compliance_percent: 0.0,
466            gaps: Vec::new(),
467            unaddressed_mandatory: Vec::new(),
468        }
469    }
470
471    /// Calculate coverage from procedure mappings.
472    pub fn calculate_coverage(&mut self, mappings: &[IsaProcedureMapping]) {
473        self.standards_addressed = mappings
474            .iter()
475            .flat_map(|m| m.standards_covered())
476            .collect();
477        self.standards_addressed.sort_by_key(|s| s.number());
478        self.standards_addressed.dedup();
479
480        // Calculate gaps
481        self.gaps = IsaStandard::all()
482            .into_iter()
483            .filter(|s| !self.standards_addressed.contains(s))
484            .collect();
485
486        // Calculate overall compliance
487        let total = IsaStandard::all().len();
488        let covered = self.standards_addressed.len();
489        self.overall_compliance_percent = (covered as f64 / total as f64) * 100.0;
490    }
491}
492
493/// Coverage summary by ISA series.
494#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct SeriesCoverage {
496    /// ISA series.
497    pub series: IsaSeries,
498
499    /// Standards in series.
500    pub total_standards: usize,
501
502    /// Standards addressed.
503    pub addressed_standards: usize,
504
505    /// Coverage percentage.
506    pub coverage_percent: f64,
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512
513    #[test]
514    fn test_isa_standard_number() {
515        assert_eq!(IsaStandard::Isa315.number(), "315");
516        assert_eq!(IsaStandard::Isa700.number(), "700");
517    }
518
519    #[test]
520    fn test_isa_standard_title() {
521        assert_eq!(
522            IsaStandard::Isa315.title(),
523            "Identifying and Assessing Risks of Material Misstatement"
524        );
525    }
526
527    #[test]
528    fn test_isa_standard_series() {
529        assert_eq!(IsaStandard::Isa200.series(), IsaSeries::GeneralPrinciples);
530        assert_eq!(IsaStandard::Isa315.series(), IsaSeries::RiskAssessment);
531        assert_eq!(IsaStandard::Isa500.series(), IsaSeries::AuditEvidence);
532        assert_eq!(IsaStandard::Isa700.series(), IsaSeries::Reporting);
533    }
534
535    #[test]
536    fn test_isa_requirement_reference() {
537        let req = IsaRequirement::new(
538            IsaStandard::Isa315,
539            "25",
540            IsaRequirementType::Requirement,
541            "Identify and assess risks of material misstatement",
542        );
543
544        assert_eq!(req.reference(), "ISA 315.25");
545        assert!(req.is_mandatory);
546    }
547
548    #[test]
549    fn test_procedure_mapping() {
550        let mut mapping =
551            IsaProcedureMapping::new(Uuid::now_v7(), "Test risk assessment procedures");
552
553        mapping.add_requirement(IsaRequirement::new(
554            IsaStandard::Isa315,
555            "25",
556            IsaRequirementType::Requirement,
557            "Risk assessment",
558        ));
559
560        mapping.add_requirement(IsaRequirement::new(
561            IsaStandard::Isa330,
562            "5",
563            IsaRequirementType::Requirement,
564            "Audit responses",
565        ));
566
567        let standards = mapping.standards_covered();
568        assert_eq!(standards.len(), 2);
569        assert!(standards.contains(&IsaStandard::Isa315));
570        assert!(standards.contains(&IsaStandard::Isa330));
571    }
572
573    #[test]
574    fn test_all_standards() {
575        let all = IsaStandard::all();
576        assert_eq!(all.len(), 34); // 34 ISA standards
577    }
578}