1use chrono::{DateTime, NaiveDate, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11use super::workpaper::Assertion;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AuditEvidence {
16 pub evidence_id: Uuid,
18 pub evidence_ref: String,
20 pub engagement_id: Uuid,
22 pub evidence_type: EvidenceType,
24 pub source_type: EvidenceSource,
26 pub title: String,
28 pub description: String,
30
31 pub obtained_date: NaiveDate,
34 pub obtained_by: String,
36 pub file_hash: Option<String>,
38 pub file_path: Option<String>,
40 pub file_size: Option<u64>,
42
43 pub reliability_assessment: ReliabilityAssessment,
46
47 pub assertions_addressed: Vec<Assertion>,
50 pub accounts_impacted: Vec<String>,
52 pub process_areas: Vec<String>,
54
55 pub linked_workpapers: Vec<Uuid>,
58 pub related_evidence: Vec<Uuid>,
60
61 pub ai_extracted_terms: Option<HashMap<String, String>>,
64 pub ai_confidence: Option<f64>,
66 pub ai_summary: Option<String>,
68
69 pub created_at: DateTime<Utc>,
71 pub updated_at: DateTime<Utc>,
72}
73
74impl AuditEvidence {
75 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 pub fn with_description(mut self, description: &str) -> Self {
112 self.description = description.into();
113 self
114 }
115
116 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 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 pub fn with_reliability(mut self, assessment: ReliabilityAssessment) -> Self {
133 self.reliability_assessment = assessment;
134 self
135 }
136
137 pub fn with_assertions(mut self, assertions: Vec<Assertion>) -> Self {
139 self.assertions_addressed = assertions;
140 self
141 }
142
143 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 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 pub fn overall_reliability(&self) -> ReliabilityLevel {
166 self.reliability_assessment.overall_reliability
167 }
168
169 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
183#[serde(rename_all = "snake_case")]
184pub enum EvidenceType {
185 Confirmation,
187 #[default]
189 Document,
190 Analysis,
192 SystemExtract,
194 Contract,
196 BankStatement,
198 Invoice,
200 Email,
202 MeetingMinutes,
204 ManagementRepresentation,
206 LegalLetter,
208 SpecialistReport,
210 PhysicalObservation,
212 Recalculation,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
218#[serde(rename_all = "snake_case")]
219pub enum EvidenceSource {
220 ExternalThirdParty,
222 #[default]
224 ExternalClientProvided,
225 InternalClientPrepared,
227 AuditorPrepared,
229}
230
231impl EvidenceSource {
232 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 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
255pub struct ReliabilityAssessment {
256 pub independence_of_source: ReliabilityLevel,
258 pub effectiveness_of_controls: ReliabilityLevel,
260 pub qualifications_of_provider: ReliabilityLevel,
262 pub objectivity_of_provider: ReliabilityLevel,
264 pub overall_reliability: ReliabilityLevel,
266 pub notes: String,
268}
269
270impl ReliabilityAssessment {
271 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 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
343#[serde(rename_all = "snake_case")]
344pub enum ReliabilityLevel {
345 High,
347 #[default]
349 Medium,
350 Low,
352}
353
354impl ReliabilityLevel {
355 pub fn score(&self) -> u8 {
357 match self {
358 Self::High => 3,
359 Self::Medium => 2,
360 Self::Low => 1,
361 }
362 }
363
364 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#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct EvidenceSufficiency {
377 pub assertion: Assertion,
379 pub account_or_area: String,
381 pub evidence_count: u32,
383 pub total_reliability_score: f64,
385 pub risk_level: super::engagement::RiskLevel,
387 pub is_sufficient: bool,
389 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}