datasynth_core/models/audit/
evidence.rs

1//! Evidence models per ISA 500.
2//!
3//! Audit evidence is all information used by the auditor to arrive at
4//! conclusions on which the auditor's opinion is based.
5
6use chrono::{DateTime, NaiveDate, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11use super::workpaper::Assertion;
12
13/// Audit evidence representing supporting documentation.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AuditEvidence {
16    /// Unique evidence ID
17    pub evidence_id: Uuid,
18    /// External reference
19    pub evidence_ref: String,
20    /// Engagement ID
21    pub engagement_id: Uuid,
22    /// Type of evidence
23    pub evidence_type: EvidenceType,
24    /// Source of evidence
25    pub source_type: EvidenceSource,
26    /// Evidence title
27    pub title: String,
28    /// Description
29    pub description: String,
30
31    // === Obtaining Information ===
32    /// Date evidence was obtained
33    pub obtained_date: NaiveDate,
34    /// Who obtained the evidence
35    pub obtained_by: String,
36    /// File hash for integrity verification
37    pub file_hash: Option<String>,
38    /// File path or storage location
39    pub file_path: Option<String>,
40    /// File size in bytes
41    pub file_size: Option<u64>,
42
43    // === Reliability Assessment per ISA 500.A31 ===
44    /// Reliability assessment
45    pub reliability_assessment: ReliabilityAssessment,
46
47    // === Relevance ===
48    /// Assertions addressed by this evidence
49    pub assertions_addressed: Vec<Assertion>,
50    /// Account IDs impacted
51    pub accounts_impacted: Vec<String>,
52    /// Process areas covered
53    pub process_areas: Vec<String>,
54
55    // === Cross-References ===
56    /// Linked workpaper IDs
57    pub linked_workpapers: Vec<Uuid>,
58    /// Related evidence IDs
59    pub related_evidence: Vec<Uuid>,
60
61    // === AI Extraction (optional) ===
62    /// AI-extracted key terms
63    pub ai_extracted_terms: Option<HashMap<String, String>>,
64    /// AI extraction confidence
65    pub ai_confidence: Option<f64>,
66    /// AI summary
67    pub ai_summary: Option<String>,
68
69    // === Metadata ===
70    pub created_at: DateTime<Utc>,
71    pub updated_at: DateTime<Utc>,
72}
73
74impl AuditEvidence {
75    /// Create new audit evidence.
76    pub fn new(
77        engagement_id: Uuid,
78        evidence_type: EvidenceType,
79        source_type: EvidenceSource,
80        title: &str,
81    ) -> Self {
82        let now = Utc::now();
83        Self {
84            evidence_id: Uuid::new_v4(),
85            evidence_ref: format!("EV-{}", Uuid::new_v4().simple()),
86            engagement_id,
87            evidence_type,
88            source_type,
89            title: title.into(),
90            description: String::new(),
91            obtained_date: now.date_naive(),
92            obtained_by: String::new(),
93            file_hash: None,
94            file_path: None,
95            file_size: None,
96            reliability_assessment: ReliabilityAssessment::default(),
97            assertions_addressed: Vec::new(),
98            accounts_impacted: Vec::new(),
99            process_areas: Vec::new(),
100            linked_workpapers: Vec::new(),
101            related_evidence: Vec::new(),
102            ai_extracted_terms: None,
103            ai_confidence: None,
104            ai_summary: None,
105            created_at: now,
106            updated_at: now,
107        }
108    }
109
110    /// Set the description.
111    pub fn with_description(mut self, description: &str) -> Self {
112        self.description = description.into();
113        self
114    }
115
116    /// Set who obtained the evidence.
117    pub fn with_obtained_by(mut self, obtained_by: &str, date: NaiveDate) -> Self {
118        self.obtained_by = obtained_by.into();
119        self.obtained_date = date;
120        self
121    }
122
123    /// Set file information.
124    pub fn with_file_info(mut self, path: &str, hash: &str, size: u64) -> Self {
125        self.file_path = Some(path.into());
126        self.file_hash = Some(hash.into());
127        self.file_size = Some(size);
128        self
129    }
130
131    /// Set reliability assessment.
132    pub fn with_reliability(mut self, assessment: ReliabilityAssessment) -> Self {
133        self.reliability_assessment = assessment;
134        self
135    }
136
137    /// Add assertions addressed.
138    pub fn with_assertions(mut self, assertions: Vec<Assertion>) -> Self {
139        self.assertions_addressed = assertions;
140        self
141    }
142
143    /// Add AI extraction results.
144    pub fn with_ai_extraction(
145        mut self,
146        terms: HashMap<String, String>,
147        confidence: f64,
148        summary: &str,
149    ) -> Self {
150        self.ai_extracted_terms = Some(terms);
151        self.ai_confidence = Some(confidence);
152        self.ai_summary = Some(summary.into());
153        self
154    }
155
156    /// Link to a workpaper.
157    pub fn link_workpaper(&mut self, workpaper_id: Uuid) {
158        if !self.linked_workpapers.contains(&workpaper_id) {
159            self.linked_workpapers.push(workpaper_id);
160            self.updated_at = Utc::now();
161        }
162    }
163
164    /// Get the overall reliability level.
165    pub fn overall_reliability(&self) -> ReliabilityLevel {
166        self.reliability_assessment.overall_reliability
167    }
168
169    /// Check if this is high-quality evidence.
170    pub fn is_high_quality(&self) -> bool {
171        matches!(
172            self.reliability_assessment.overall_reliability,
173            ReliabilityLevel::High
174        ) && matches!(
175            self.source_type,
176            EvidenceSource::ExternalThirdParty | EvidenceSource::AuditorPrepared
177        )
178    }
179}
180
181/// Type of audit evidence.
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
183#[serde(rename_all = "snake_case")]
184pub enum EvidenceType {
185    /// Confirmation from third party
186    Confirmation,
187    /// Client-prepared document
188    #[default]
189    Document,
190    /// Auditor-prepared analysis
191    Analysis,
192    /// Screenshot or system extract
193    SystemExtract,
194    /// Contract or agreement
195    Contract,
196    /// Bank statement
197    BankStatement,
198    /// Invoice
199    Invoice,
200    /// Email correspondence
201    Email,
202    /// Meeting minutes
203    MeetingMinutes,
204    /// Management representation
205    ManagementRepresentation,
206    /// Legal letter
207    LegalLetter,
208    /// Specialist report
209    SpecialistReport,
210    /// Physical inventory observation
211    PhysicalObservation,
212    /// Recalculation spreadsheet
213    Recalculation,
214}
215
216/// Source of audit evidence per ISA 500.A31.
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
218#[serde(rename_all = "snake_case")]
219pub enum EvidenceSource {
220    /// External source, directly from third party
221    ExternalThirdParty,
222    /// External source, provided by client
223    #[default]
224    ExternalClientProvided,
225    /// Internal source, client prepared
226    InternalClientPrepared,
227    /// Auditor prepared
228    AuditorPrepared,
229}
230
231impl EvidenceSource {
232    /// Get the inherent reliability of this source type.
233    pub fn inherent_reliability(&self) -> ReliabilityLevel {
234        match self {
235            Self::ExternalThirdParty => ReliabilityLevel::High,
236            Self::AuditorPrepared => ReliabilityLevel::High,
237            Self::ExternalClientProvided => ReliabilityLevel::Medium,
238            Self::InternalClientPrepared => ReliabilityLevel::Low,
239        }
240    }
241
242    /// Get a description for ISA documentation.
243    pub fn description(&self) -> &'static str {
244        match self {
245            Self::ExternalThirdParty => "Obtained directly from independent external source",
246            Self::AuditorPrepared => "Prepared by the auditor",
247            Self::ExternalClientProvided => "External evidence provided by client",
248            Self::InternalClientPrepared => "Prepared internally by client personnel",
249        }
250    }
251}
252
253/// Reliability assessment per ISA 500.A31.
254#[derive(Debug, Clone, Serialize, Deserialize, Default)]
255pub struct ReliabilityAssessment {
256    /// Independence of source
257    pub independence_of_source: ReliabilityLevel,
258    /// Effectiveness of related controls
259    pub effectiveness_of_controls: ReliabilityLevel,
260    /// Qualifications of information provider
261    pub qualifications_of_provider: ReliabilityLevel,
262    /// Objectivity of information provider
263    pub objectivity_of_provider: ReliabilityLevel,
264    /// Overall reliability conclusion
265    pub overall_reliability: ReliabilityLevel,
266    /// Assessment notes
267    pub notes: String,
268}
269
270impl ReliabilityAssessment {
271    /// Create a new reliability assessment.
272    pub fn new(
273        independence: ReliabilityLevel,
274        controls: ReliabilityLevel,
275        qualifications: ReliabilityLevel,
276        objectivity: ReliabilityLevel,
277        notes: &str,
278    ) -> Self {
279        let overall = Self::calculate_overall(independence, controls, qualifications, objectivity);
280        Self {
281            independence_of_source: independence,
282            effectiveness_of_controls: controls,
283            qualifications_of_provider: qualifications,
284            objectivity_of_provider: objectivity,
285            overall_reliability: overall,
286            notes: notes.into(),
287        }
288    }
289
290    /// Calculate overall reliability from components.
291    fn calculate_overall(
292        independence: ReliabilityLevel,
293        controls: ReliabilityLevel,
294        qualifications: ReliabilityLevel,
295        objectivity: ReliabilityLevel,
296    ) -> ReliabilityLevel {
297        let scores = [
298            independence.score(),
299            controls.score(),
300            qualifications.score(),
301            objectivity.score(),
302        ];
303        let avg = scores.iter().sum::<u8>() / 4;
304        ReliabilityLevel::from_score(avg)
305    }
306
307    /// Create a high reliability assessment.
308    pub fn high(notes: &str) -> Self {
309        Self::new(
310            ReliabilityLevel::High,
311            ReliabilityLevel::High,
312            ReliabilityLevel::High,
313            ReliabilityLevel::High,
314            notes,
315        )
316    }
317
318    /// Create a medium reliability assessment.
319    pub fn medium(notes: &str) -> Self {
320        Self::new(
321            ReliabilityLevel::Medium,
322            ReliabilityLevel::Medium,
323            ReliabilityLevel::Medium,
324            ReliabilityLevel::Medium,
325            notes,
326        )
327    }
328
329    /// Create a low reliability assessment.
330    pub fn low(notes: &str) -> Self {
331        Self::new(
332            ReliabilityLevel::Low,
333            ReliabilityLevel::Low,
334            ReliabilityLevel::Low,
335            ReliabilityLevel::Low,
336            notes,
337        )
338    }
339}
340
341/// Reliability level for evidence assessment.
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
343#[serde(rename_all = "snake_case")]
344pub enum ReliabilityLevel {
345    /// High reliability
346    High,
347    /// Medium reliability
348    #[default]
349    Medium,
350    /// Low reliability
351    Low,
352}
353
354impl ReliabilityLevel {
355    /// Get numeric score.
356    pub fn score(&self) -> u8 {
357        match self {
358            Self::High => 3,
359            Self::Medium => 2,
360            Self::Low => 1,
361        }
362    }
363
364    /// Create from score.
365    pub fn from_score(score: u8) -> Self {
366        match score {
367            0..=1 => Self::Low,
368            2 => Self::Medium,
369            _ => Self::High,
370        }
371    }
372}
373
374/// Evidence sufficiency evaluation per ISA 500.
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct EvidenceSufficiency {
377    /// Assertion being evaluated
378    pub assertion: Assertion,
379    /// Account or area
380    pub account_or_area: String,
381    /// Evidence pieces collected
382    pub evidence_count: u32,
383    /// Total reliability score
384    pub total_reliability_score: f64,
385    /// Risk level being addressed
386    pub risk_level: super::engagement::RiskLevel,
387    /// Is evidence sufficient?
388    pub is_sufficient: bool,
389    /// Sufficiency conclusion notes
390    pub conclusion_notes: String,
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn test_evidence_creation() {
399        let evidence = AuditEvidence::new(
400            Uuid::new_v4(),
401            EvidenceType::Confirmation,
402            EvidenceSource::ExternalThirdParty,
403            "Bank Confirmation",
404        );
405
406        assert_eq!(evidence.evidence_type, EvidenceType::Confirmation);
407        assert_eq!(evidence.source_type, EvidenceSource::ExternalThirdParty);
408    }
409
410    #[test]
411    fn test_reliability_assessment() {
412        let assessment = ReliabilityAssessment::new(
413            ReliabilityLevel::High,
414            ReliabilityLevel::Medium,
415            ReliabilityLevel::High,
416            ReliabilityLevel::Medium,
417            "External confirmation with good controls",
418        );
419
420        assert_eq!(assessment.overall_reliability, ReliabilityLevel::Medium);
421    }
422
423    #[test]
424    fn test_source_reliability() {
425        assert_eq!(
426            EvidenceSource::ExternalThirdParty.inherent_reliability(),
427            ReliabilityLevel::High
428        );
429        assert_eq!(
430            EvidenceSource::InternalClientPrepared.inherent_reliability(),
431            ReliabilityLevel::Low
432        );
433    }
434
435    #[test]
436    fn test_evidence_quality() {
437        let evidence = AuditEvidence::new(
438            Uuid::new_v4(),
439            EvidenceType::Confirmation,
440            EvidenceSource::ExternalThirdParty,
441            "Bank Confirmation",
442        )
443        .with_reliability(ReliabilityAssessment::high("Direct confirmation"));
444
445        assert!(evidence.is_high_quality());
446    }
447}