1use chrono::NaiveDate;
10use rust_decimal::Decimal;
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Sox302Certification {
17 pub certification_id: Uuid,
19
20 pub company_code: String,
22
23 pub fiscal_year: u16,
25
26 pub period_end_date: NaiveDate,
28
29 pub certifier_role: CertifierRole,
31
32 pub certifier_name: String,
34
35 pub certification_date: NaiveDate,
37
38 pub report_type: ReportType,
40
41 pub reviewed_report: bool,
44
45 pub no_material_misstatement: bool,
47
48 pub fairly_presented: bool,
50
51 pub disclosure_controls_effective: bool,
54
55 pub disclosure_controls_evaluation_date: Option<NaiveDate>,
57
58 pub internal_control_designed_effectively: bool,
61
62 pub significant_changes_disclosed: bool,
65
66 pub significant_changes_description: Option<String>,
68
69 pub fraud_disclosed: bool,
72
73 pub fraud_description: Option<String>,
75
76 pub material_weaknesses: Vec<Uuid>, pub significant_deficiencies: Vec<Uuid>,
81
82 pub certification_text: String,
84}
85
86impl Sox302Certification {
87 pub fn new(
89 company_code: impl Into<String>,
90 fiscal_year: u16,
91 period_end_date: NaiveDate,
92 certifier_role: CertifierRole,
93 certifier_name: impl Into<String>,
94 ) -> Self {
95 Self {
96 certification_id: Uuid::now_v7(),
97 company_code: company_code.into(),
98 fiscal_year,
99 period_end_date,
100 certifier_role,
101 certifier_name: certifier_name.into(),
102 certification_date: chrono::Utc::now().date_naive(),
103 report_type: ReportType::AnnualReport10K,
104 reviewed_report: true,
105 no_material_misstatement: true,
106 fairly_presented: true,
107 disclosure_controls_effective: true,
108 disclosure_controls_evaluation_date: Some(period_end_date),
109 internal_control_designed_effectively: true,
110 significant_changes_disclosed: true,
111 significant_changes_description: None,
112 fraud_disclosed: false,
113 fraud_description: None,
114 material_weaknesses: Vec::new(),
115 significant_deficiencies: Vec::new(),
116 certification_text: String::new(),
117 }
118 }
119
120 pub fn generate_certification_text(&mut self) {
122 let report_name = match self.report_type {
123 ReportType::AnnualReport10K => "annual report on Form 10-K",
124 ReportType::QuarterlyReport10Q => "quarterly report on Form 10-Q",
125 };
126
127 self.certification_text = format!(
128 "I, {}, certify that:\n\n\
129 1. I have reviewed this {} of {};\n\n\
130 2. Based on my knowledge, this report does not contain any untrue statement of a \
131 material fact or omit to state a material fact necessary to make the statements \
132 made, in light of the circumstances under which such statements were made, not \
133 misleading with respect to the period covered by this report;\n\n\
134 3. Based on my knowledge, the financial statements, and other financial information \
135 included in this report, fairly present in all material respects the financial \
136 condition, results of operations and cash flows of the registrant as of, and for, \
137 the periods presented in this report;\n\n\
138 4. The registrant's other certifying officer and I are responsible for establishing \
139 and maintaining disclosure controls and procedures (as defined in Exchange Act \
140 Rules 13a-15(e) and 15d-15(e)) and internal control over financial reporting \
141 (as defined in Exchange Act Rules 13a-15(f) and 15d-15(f)) for the registrant \
142 and have:\n\n\
143 (a) Designed such disclosure controls and procedures, or caused such disclosure \
144 controls and procedures to be designed under our supervision, to ensure that \
145 material information relating to the registrant is made known to us;\n\n\
146 (b) Designed such internal control over financial reporting, or caused such \
147 internal control over financial reporting to be designed under our supervision, \
148 to provide reasonable assurance regarding the reliability of financial reporting;\n\n\
149 (c) Evaluated the effectiveness of the registrant's disclosure controls and \
150 procedures and presented in this report our conclusions about the effectiveness \
151 of the disclosure controls and procedures;\n\n\
152 (d) Disclosed in this report any change in the registrant's internal control over \
153 financial reporting that occurred during the registrant's most recent fiscal \
154 quarter that has materially affected, or is reasonably likely to materially \
155 affect, the registrant's internal control over financial reporting;\n\n\
156 5. The registrant's other certifying officer and I have disclosed, based on our most \
157 recent evaluation of internal control over financial reporting, to the registrant's \
158 auditors and the audit committee of the registrant's board of directors:\n\n\
159 (a) All significant deficiencies and material weaknesses in the design or operation \
160 of internal control over financial reporting which are reasonably likely to \
161 adversely affect the registrant's ability to record, process, summarize and \
162 report financial information; and\n\n\
163 (b) Any fraud, whether or not material, that involves management or other employees \
164 who have a significant role in the registrant's internal control over financial \
165 reporting.",
166 self.certifier_name,
167 report_name,
168 self.company_code
169 );
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
175#[serde(rename_all = "snake_case")]
176pub enum CertifierRole {
177 Ceo,
179 Cfo,
181 PrincipalExecutiveOfficer,
183 PrincipalFinancialOfficer,
185}
186
187impl std::fmt::Display for CertifierRole {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 match self {
190 Self::Ceo => write!(f, "Chief Executive Officer"),
191 Self::Cfo => write!(f, "Chief Financial Officer"),
192 Self::PrincipalExecutiveOfficer => write!(f, "Principal Executive Officer"),
193 Self::PrincipalFinancialOfficer => write!(f, "Principal Financial Officer"),
194 }
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
200#[serde(rename_all = "snake_case")]
201pub enum ReportType {
202 #[default]
204 AnnualReport10K,
205 QuarterlyReport10Q,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct Sox404Assessment {
212 pub assessment_id: Uuid,
214
215 pub company_code: String,
217
218 pub fiscal_year: u16,
220
221 pub assessment_date: NaiveDate,
223
224 pub framework: IcfrFramework,
226
227 pub icfr_effective: bool,
229
230 pub scope: Vec<ScopedEntity>,
232
233 #[serde(with = "rust_decimal::serde::str")]
235 pub materiality_threshold: Decimal,
236
237 pub key_controls_tested: usize,
239
240 pub key_controls_effective: usize,
242
243 pub deficiency_classification: DeficiencyClassificationSummary,
245
246 pub material_weaknesses: Vec<MaterialWeakness>,
248
249 pub significant_deficiencies: Vec<SignificantDeficiency>,
251
252 pub control_deficiencies: Vec<ControlDeficiency>,
254
255 pub remediation_actions: Vec<RemediationAction>,
257
258 pub management_conclusion: String,
260
261 pub management_report_date: NaiveDate,
263}
264
265impl Sox404Assessment {
266 pub fn new(
268 company_code: impl Into<String>,
269 fiscal_year: u16,
270 assessment_date: NaiveDate,
271 ) -> Self {
272 Self {
273 assessment_id: Uuid::now_v7(),
274 company_code: company_code.into(),
275 fiscal_year,
276 assessment_date,
277 framework: IcfrFramework::Coso2013,
278 icfr_effective: true,
279 scope: Vec::new(),
280 materiality_threshold: Decimal::ZERO,
281 key_controls_tested: 0,
282 key_controls_effective: 0,
283 deficiency_classification: DeficiencyClassificationSummary::default(),
284 material_weaknesses: Vec::new(),
285 significant_deficiencies: Vec::new(),
286 control_deficiencies: Vec::new(),
287 remediation_actions: Vec::new(),
288 management_conclusion: String::new(),
289 management_report_date: assessment_date,
290 }
291 }
292
293 pub fn evaluate_effectiveness(&mut self) {
295 self.icfr_effective = self.material_weaknesses.is_empty();
297 }
298
299 pub fn effectiveness_rate(&self) -> f64 {
301 if self.key_controls_tested == 0 {
302 return 0.0;
303 }
304 (self.key_controls_effective as f64 / self.key_controls_tested as f64) * 100.0
305 }
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
310#[serde(rename_all = "snake_case")]
311pub enum IcfrFramework {
312 #[default]
314 Coso2013,
315 Coso1992,
317 Other,
319}
320
321impl std::fmt::Display for IcfrFramework {
322 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
323 match self {
324 Self::Coso2013 => write!(f, "COSO 2013"),
325 Self::Coso1992 => write!(f, "COSO 1992"),
326 Self::Other => write!(f, "Other Framework"),
327 }
328 }
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct ScopedEntity {
334 pub entity_code: String,
336
337 pub entity_name: String,
339
340 #[serde(with = "rust_decimal::serde::str")]
342 pub revenue_percent: Decimal,
343
344 #[serde(with = "rust_decimal::serde::str")]
346 pub assets_percent: Decimal,
347
348 pub scope_conclusion: ScopeConclusion,
350
351 pub significant_accounts: Vec<String>,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
357#[serde(rename_all = "snake_case")]
358pub enum ScopeConclusion {
359 #[default]
361 InScope,
362 OutOfScope,
364 SpecificAccountsOnly,
366 CommonControl,
368}
369
370#[derive(Debug, Clone, Default, Serialize, Deserialize)]
372pub struct DeficiencyClassificationSummary {
373 pub deficiencies_identified: u32,
375
376 pub control_deficiencies: u32,
378
379 pub significant_deficiencies: u32,
381
382 pub material_weaknesses: u32,
384
385 pub remediated: u32,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
391pub struct DeficiencyMatrix {
392 pub likelihood: DeficiencyLikelihood,
394
395 pub magnitude: DeficiencyMagnitude,
397
398 pub classification: DeficiencyClassification,
400}
401
402impl DeficiencyMatrix {
403 pub fn classify(
405 likelihood: DeficiencyLikelihood,
406 magnitude: DeficiencyMagnitude,
407 ) -> DeficiencyClassification {
408 match (likelihood, magnitude) {
409 (DeficiencyLikelihood::Probable, DeficiencyMagnitude::Material) => {
411 DeficiencyClassification::MaterialWeakness
412 }
413 (DeficiencyLikelihood::ReasonablyPossible, DeficiencyMagnitude::Material) => {
414 DeficiencyClassification::MaterialWeakness
415 }
416 (DeficiencyLikelihood::Probable, DeficiencyMagnitude::MoreThanInconsequential) => {
417 DeficiencyClassification::MaterialWeakness
418 }
419
420 (
422 DeficiencyLikelihood::ReasonablyPossible,
423 DeficiencyMagnitude::MoreThanInconsequential,
424 ) => DeficiencyClassification::SignificantDeficiency,
425 (DeficiencyLikelihood::Probable, DeficiencyMagnitude::Inconsequential) => {
426 DeficiencyClassification::SignificantDeficiency
427 }
428
429 _ => DeficiencyClassification::ControlDeficiency,
431 }
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
437#[serde(rename_all = "snake_case")]
438pub enum DeficiencyLikelihood {
439 Remote,
441 #[default]
443 ReasonablyPossible,
444 Probable,
446}
447
448impl std::fmt::Display for DeficiencyLikelihood {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450 match self {
451 Self::Remote => write!(f, "Remote"),
452 Self::ReasonablyPossible => write!(f, "Reasonably Possible"),
453 Self::Probable => write!(f, "Probable"),
454 }
455 }
456}
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
460#[serde(rename_all = "snake_case")]
461pub enum DeficiencyMagnitude {
462 Inconsequential,
464 #[default]
466 MoreThanInconsequential,
467 Material,
469}
470
471impl std::fmt::Display for DeficiencyMagnitude {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 match self {
474 Self::Inconsequential => write!(f, "Inconsequential"),
475 Self::MoreThanInconsequential => write!(f, "More Than Inconsequential"),
476 Self::Material => write!(f, "Material"),
477 }
478 }
479}
480
481#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
483#[serde(rename_all = "snake_case")]
484pub enum DeficiencyClassification {
485 #[default]
487 ControlDeficiency,
488 SignificantDeficiency,
490 MaterialWeakness,
492}
493
494impl std::fmt::Display for DeficiencyClassification {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 match self {
497 Self::ControlDeficiency => write!(f, "Control Deficiency"),
498 Self::SignificantDeficiency => write!(f, "Significant Deficiency"),
499 Self::MaterialWeakness => write!(f, "Material Weakness"),
500 }
501 }
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct MaterialWeakness {
507 pub weakness_id: Uuid,
509
510 pub description: String,
512
513 pub affected_controls: Vec<String>,
515
516 pub affected_accounts: Vec<String>,
518
519 pub related_assertions: Vec<String>,
521
522 pub root_cause: String,
524
525 #[serde(default, with = "rust_decimal::serde::str_option")]
527 pub potential_misstatement: Option<Decimal>,
528
529 pub likelihood: DeficiencyLikelihood,
531
532 pub magnitude: DeficiencyMagnitude,
534
535 pub identification_date: NaiveDate,
537
538 pub remediated_by_year_end: bool,
540
541 pub remediation_date: Option<NaiveDate>,
543
544 pub related_finding_ids: Vec<Uuid>,
546}
547
548impl MaterialWeakness {
549 pub fn new(description: impl Into<String>, identification_date: NaiveDate) -> Self {
551 Self {
552 weakness_id: Uuid::now_v7(),
553 description: description.into(),
554 affected_controls: Vec::new(),
555 affected_accounts: Vec::new(),
556 related_assertions: Vec::new(),
557 root_cause: String::new(),
558 potential_misstatement: None,
559 likelihood: DeficiencyLikelihood::Probable,
560 magnitude: DeficiencyMagnitude::Material,
561 identification_date,
562 remediated_by_year_end: false,
563 remediation_date: None,
564 related_finding_ids: Vec::new(),
565 }
566 }
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize)]
571pub struct SignificantDeficiency {
572 pub deficiency_id: Uuid,
574
575 pub description: String,
577
578 pub affected_controls: Vec<String>,
580
581 pub affected_accounts: Vec<String>,
583
584 pub likelihood: DeficiencyLikelihood,
586
587 pub magnitude: DeficiencyMagnitude,
589
590 pub identification_date: NaiveDate,
592
593 pub remediated: bool,
595}
596
597impl SignificantDeficiency {
598 pub fn new(description: impl Into<String>, identification_date: NaiveDate) -> Self {
600 Self {
601 deficiency_id: Uuid::now_v7(),
602 description: description.into(),
603 affected_controls: Vec::new(),
604 affected_accounts: Vec::new(),
605 likelihood: DeficiencyLikelihood::ReasonablyPossible,
606 magnitude: DeficiencyMagnitude::MoreThanInconsequential,
607 identification_date,
608 remediated: false,
609 }
610 }
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct ControlDeficiency {
616 pub deficiency_id: Uuid,
618
619 pub description: String,
621
622 pub affected_control: String,
624
625 pub identification_date: NaiveDate,
627
628 pub remediated: bool,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct RemediationAction {
635 pub action_id: Uuid,
637
638 pub deficiency_id: Uuid,
640
641 pub description: String,
643
644 pub responsible_party: String,
646
647 pub target_date: NaiveDate,
649
650 pub completion_date: Option<NaiveDate>,
652
653 pub status: RemediationStatus,
655
656 pub remediation_tested: bool,
658
659 pub remediation_effective: bool,
661}
662
663#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
665#[serde(rename_all = "snake_case")]
666pub enum RemediationStatus {
667 #[default]
669 NotStarted,
670 InProgress,
672 Completed,
674 Deferred,
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use rust_decimal_macros::dec;
682
683 #[test]
684 fn test_sox_302_certification() {
685 let mut cert = Sox302Certification::new(
686 "ABC Corp",
687 2024,
688 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
689 CertifierRole::Ceo,
690 "John Smith",
691 );
692
693 cert.generate_certification_text();
694
695 assert!(!cert.certification_text.is_empty());
696 assert!(cert.certification_text.contains("John Smith"));
697 assert!(cert.disclosure_controls_effective);
698 }
699
700 #[test]
701 fn test_sox_404_assessment() {
702 let mut assessment = Sox404Assessment::new(
703 "ABC Corp",
704 2024,
705 NaiveDate::from_ymd_opt(2025, 2, 28).unwrap(),
706 );
707
708 assessment.key_controls_tested = 100;
709 assessment.key_controls_effective = 95;
710 assessment.materiality_threshold = dec!(100000);
711
712 assert_eq!(assessment.effectiveness_rate(), 95.0);
713 assert!(assessment.icfr_effective);
714 }
715
716 #[test]
717 fn test_sox_404_with_material_weakness() {
718 let mut assessment = Sox404Assessment::new(
719 "ABC Corp",
720 2024,
721 NaiveDate::from_ymd_opt(2025, 2, 28).unwrap(),
722 );
723
724 assessment.material_weaknesses.push(MaterialWeakness::new(
725 "Inadequate segregation of duties in accounts payable",
726 NaiveDate::from_ymd_opt(2024, 9, 30).unwrap(),
727 ));
728
729 assessment.evaluate_effectiveness();
730
731 assert!(!assessment.icfr_effective);
732 }
733
734 #[test]
735 fn test_deficiency_matrix_classification() {
736 assert_eq!(
738 DeficiencyMatrix::classify(
739 DeficiencyLikelihood::Probable,
740 DeficiencyMagnitude::Material
741 ),
742 DeficiencyClassification::MaterialWeakness
743 );
744 assert_eq!(
745 DeficiencyMatrix::classify(
746 DeficiencyLikelihood::ReasonablyPossible,
747 DeficiencyMagnitude::Material
748 ),
749 DeficiencyClassification::MaterialWeakness
750 );
751
752 assert_eq!(
754 DeficiencyMatrix::classify(
755 DeficiencyLikelihood::ReasonablyPossible,
756 DeficiencyMagnitude::MoreThanInconsequential
757 ),
758 DeficiencyClassification::SignificantDeficiency
759 );
760
761 assert_eq!(
763 DeficiencyMatrix::classify(
764 DeficiencyLikelihood::Remote,
765 DeficiencyMagnitude::Inconsequential
766 ),
767 DeficiencyClassification::ControlDeficiency
768 );
769 }
770}