1use rust_decimal::Decimal;
7use rust_decimal_macros::dec;
8use serde::{Deserialize, Serialize};
9use std::collections::HashSet;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct StandardsComplianceEvaluation {
18 pub revenue_recognition: Option<RevenueRecognitionEvaluation>,
20 pub lease_accounting: Option<LeaseAccountingEvaluation>,
22 pub fair_value: Option<FairValueEvaluation>,
24 pub impairment: Option<ImpairmentEvaluation>,
26 pub isa_compliance: Option<IsaComplianceEvaluation>,
28 pub sox_compliance: Option<SoxComplianceEvaluation>,
30 pub pcaob_compliance: Option<PcaobComplianceEvaluation>,
32 pub audit_trail: Option<AuditTrailEvaluation>,
34 pub passes: bool,
36 pub failures: Vec<String>,
38 pub warnings: Vec<String>,
40}
41
42impl StandardsComplianceEvaluation {
43 pub fn new() -> Self {
45 Self {
46 revenue_recognition: None,
47 lease_accounting: None,
48 fair_value: None,
49 impairment: None,
50 isa_compliance: None,
51 sox_compliance: None,
52 pcaob_compliance: None,
53 audit_trail: None,
54 passes: true,
55 failures: Vec::new(),
56 warnings: Vec::new(),
57 }
58 }
59
60 pub fn check_thresholds(&mut self, thresholds: &StandardsThresholds) {
62 self.failures.clear();
63 self.warnings.clear();
64
65 if let Some(ref rev) = self.revenue_recognition {
67 if rev.po_allocation_compliance < thresholds.min_po_allocation_compliance {
68 self.failures.push(format!(
69 "PO allocation compliance {:.2}% < {:.2}% (threshold)",
70 rev.po_allocation_compliance * 100.0,
71 thresholds.min_po_allocation_compliance * 100.0
72 ));
73 }
74 if rev.timing_compliance < thresholds.min_revenue_timing_compliance {
75 self.failures.push(format!(
76 "Revenue timing compliance {:.2}% < {:.2}% (threshold)",
77 rev.timing_compliance * 100.0,
78 thresholds.min_revenue_timing_compliance * 100.0
79 ));
80 }
81 }
82
83 if let Some(ref lease) = self.lease_accounting {
85 if lease.classification_accuracy < thresholds.min_lease_classification_accuracy {
86 self.failures.push(format!(
87 "Lease classification accuracy {:.2}% < {:.2}% (threshold)",
88 lease.classification_accuracy * 100.0,
89 thresholds.min_lease_classification_accuracy * 100.0
90 ));
91 }
92 if lease.rou_asset_accuracy < thresholds.min_rou_asset_accuracy {
93 self.failures.push(format!(
94 "ROU asset calculation accuracy {:.2}% < {:.2}% (threshold)",
95 lease.rou_asset_accuracy * 100.0,
96 thresholds.min_rou_asset_accuracy * 100.0
97 ));
98 }
99 }
100
101 if let Some(ref fv) = self.fair_value {
103 if fv.hierarchy_compliance < thresholds.min_fair_value_hierarchy_compliance {
104 self.failures.push(format!(
105 "Fair value hierarchy compliance {:.2}% < {:.2}% (threshold)",
106 fv.hierarchy_compliance * 100.0,
107 thresholds.min_fair_value_hierarchy_compliance * 100.0
108 ));
109 }
110 }
111
112 if let Some(ref imp) = self.impairment {
114 if imp.trigger_recognition_rate < thresholds.min_impairment_trigger_rate {
115 self.warnings.push(format!(
116 "Impairment trigger recognition rate {:.2}% < {:.2}% (warning)",
117 imp.trigger_recognition_rate * 100.0,
118 thresholds.min_impairment_trigger_rate * 100.0
119 ));
120 }
121 }
122
123 if let Some(ref isa) = self.isa_compliance {
125 if isa.coverage_rate < thresholds.min_isa_coverage {
126 self.failures.push(format!(
127 "ISA coverage rate {:.2}% < {:.2}% (threshold)",
128 isa.coverage_rate * 100.0,
129 thresholds.min_isa_coverage * 100.0
130 ));
131 }
132 }
133
134 if let Some(ref sox) = self.sox_compliance {
136 if sox.control_coverage < thresholds.min_sox_control_coverage {
137 self.failures.push(format!(
138 "SOX control coverage {:.2}% < {:.2}% (threshold)",
139 sox.control_coverage * 100.0,
140 thresholds.min_sox_control_coverage * 100.0
141 ));
142 }
143 }
144
145 if let Some(ref trail) = self.audit_trail {
147 if trail.completeness < thresholds.min_audit_trail_completeness {
148 self.failures.push(format!(
149 "Audit trail completeness {:.2}% < {:.2}% (threshold)",
150 trail.completeness * 100.0,
151 thresholds.min_audit_trail_completeness * 100.0
152 ));
153 }
154 }
155
156 self.passes = self.failures.is_empty();
157 }
158}
159
160impl Default for StandardsComplianceEvaluation {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct RevenueRecognitionEvaluation {
173 pub total_contracts: usize,
175 pub valid_contracts: usize,
177 pub po_allocation_compliance: f64,
179 pub timing_compliance: f64,
181 pub variable_consideration_compliance: f64,
183 pub modification_compliance: f64,
185 pub framework_violations: Vec<FrameworkViolation>,
187 pub balanced_contracts: usize,
189}
190
191impl RevenueRecognitionEvaluation {
192 pub fn new() -> Self {
194 Self {
195 total_contracts: 0,
196 valid_contracts: 0,
197 po_allocation_compliance: 1.0,
198 timing_compliance: 1.0,
199 variable_consideration_compliance: 1.0,
200 modification_compliance: 1.0,
201 framework_violations: Vec::new(),
202 balanced_contracts: 0,
203 }
204 }
205}
206
207impl Default for RevenueRecognitionEvaluation {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213pub struct RevenueRecognitionEvaluator;
215
216impl RevenueRecognitionEvaluator {
217 pub fn evaluate(contracts: &[RevenueContract]) -> RevenueRecognitionEvaluation {
219 let mut eval = RevenueRecognitionEvaluation::new();
220 eval.total_contracts = contracts.len();
221
222 if contracts.is_empty() {
223 return eval;
224 }
225
226 let mut valid_count = 0;
227 let mut po_compliant = 0;
228 let mut timing_compliant = 0;
229 let mut vc_compliant = 0;
230 let mut balanced = 0;
231
232 for contract in contracts {
233 if contract.is_valid() {
235 valid_count += 1;
236 }
237
238 if contract.check_po_allocation() {
240 po_compliant += 1;
241 } else {
242 eval.framework_violations.push(FrameworkViolation {
243 standard: "ASC 606-10-32-28".to_string(),
244 description: format!(
245 "Contract {} PO allocation doesn't equal transaction price",
246 contract.contract_id
247 ),
248 severity: ViolationSeverity::Error,
249 });
250 }
251
252 if contract.check_revenue_timing() {
254 timing_compliant += 1;
255 }
256
257 if contract.variable_consideration.is_none() || contract.check_variable_consideration()
259 {
260 vc_compliant += 1;
261 }
262
263 if contract.check_balance() {
265 balanced += 1;
266 }
267 }
268
269 eval.valid_contracts = valid_count;
270 eval.po_allocation_compliance = po_compliant as f64 / contracts.len() as f64;
271 eval.timing_compliance = timing_compliant as f64 / contracts.len() as f64;
272 eval.variable_consideration_compliance = vc_compliant as f64 / contracts.len() as f64;
273 eval.balanced_contracts = balanced;
274
275 eval
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct RevenueContract {
282 pub contract_id: String,
284 pub transaction_price: Decimal,
286 pub performance_obligations: Vec<PerformanceObligation>,
288 pub variable_consideration: Option<VariableConsideration>,
290 pub revenue_recognized: Decimal,
292 pub deferred_revenue: Decimal,
294}
295
296impl RevenueContract {
297 pub fn is_valid(&self) -> bool {
299 !self.performance_obligations.is_empty() && self.transaction_price > dec!(0)
300 }
301
302 pub fn check_po_allocation(&self) -> bool {
304 let allocated: Decimal = self
305 .performance_obligations
306 .iter()
307 .map(|po| po.allocated_amount)
308 .sum();
309 (allocated - self.transaction_price).abs() < dec!(0.01)
310 }
311
312 pub fn check_revenue_timing(&self) -> bool {
314 for po in &self.performance_obligations {
315 let expected_revenue = po.allocated_amount * po.satisfaction_percent;
316 let tolerance = po.allocated_amount * dec!(0.01); if (po.recognized_revenue - expected_revenue).abs() > tolerance {
318 return false;
319 }
320 }
321 true
322 }
323
324 pub fn check_variable_consideration(&self) -> bool {
326 if let Some(ref vc) = self.variable_consideration {
327 vc.constrained_amount <= vc.expected_amount
329 } else {
330 true
331 }
332 }
333
334 pub fn check_balance(&self) -> bool {
336 let total = self.revenue_recognized + self.deferred_revenue;
337 (total - self.transaction_price).abs() < dec!(0.01)
338 }
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct PerformanceObligation {
344 pub obligation_id: String,
346 pub allocated_amount: Decimal,
348 pub satisfaction_percent: Decimal,
350 pub recognized_revenue: Decimal,
352 pub over_time: bool,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct VariableConsideration {
359 pub expected_amount: Decimal,
361 pub constrained_amount: Decimal,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct LeaseAccountingEvaluation {
372 pub total_leases: usize,
374 pub finance_leases: usize,
376 pub operating_leases: usize,
378 pub classification_accuracy: f64,
380 pub rou_asset_accuracy: f64,
382 pub lease_liability_accuracy: f64,
384 pub discount_rate_reasonableness: f64,
386 pub framework_violations: Vec<FrameworkViolation>,
388}
389
390impl LeaseAccountingEvaluation {
391 pub fn new() -> Self {
393 Self {
394 total_leases: 0,
395 finance_leases: 0,
396 operating_leases: 0,
397 classification_accuracy: 1.0,
398 rou_asset_accuracy: 1.0,
399 lease_liability_accuracy: 1.0,
400 discount_rate_reasonableness: 1.0,
401 framework_violations: Vec::new(),
402 }
403 }
404}
405
406impl Default for LeaseAccountingEvaluation {
407 fn default() -> Self {
408 Self::new()
409 }
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct LeaseEvaluation {
415 pub lease_id: String,
417 pub lease_term_months: u32,
419 pub asset_useful_life_months: u32,
421 pub pv_ratio: f64,
423 pub is_finance: bool,
425 pub framework: String,
427 pub rou_asset_initial: Decimal,
429 pub lease_liability_initial: Decimal,
431 pub discount_rate: f64,
433}
434
435impl LeaseEvaluation {
436 pub fn check_us_gaap_classification(&self) -> bool {
438 let term_ratio = self.lease_term_months as f64 / self.asset_useful_life_months as f64;
439
440 let should_be_finance = term_ratio >= 0.75 || self.pv_ratio >= 0.90;
442
443 self.is_finance == should_be_finance
444 }
445
446 pub fn check_ifrs_classification(&self) -> bool {
448 let term_ratio = self.lease_term_months as f64 / self.asset_useful_life_months as f64;
450
451 let indicators_suggest_finance = term_ratio >= 0.75 || self.pv_ratio >= 0.90;
453
454 self.is_finance == indicators_suggest_finance
456 }
457
458 pub fn check_rou_equals_liability(&self) -> bool {
460 let diff = (self.rou_asset_initial - self.lease_liability_initial).abs();
462 diff < self.lease_liability_initial * dec!(0.05) }
464}
465
466pub struct LeaseAccountingEvaluator;
468
469impl LeaseAccountingEvaluator {
470 pub fn evaluate(leases: &[LeaseEvaluation], framework: &str) -> LeaseAccountingEvaluation {
472 let mut eval = LeaseAccountingEvaluation::new();
473 eval.total_leases = leases.len();
474
475 if leases.is_empty() {
476 return eval;
477 }
478
479 let mut classification_correct = 0;
480 let mut rou_accurate = 0;
481
482 for lease in leases {
483 if lease.is_finance {
484 eval.finance_leases += 1;
485 } else {
486 eval.operating_leases += 1;
487 }
488
489 let classification_ok = if framework == "us_gaap" {
491 lease.check_us_gaap_classification()
492 } else {
493 lease.check_ifrs_classification()
494 };
495
496 if classification_ok {
497 classification_correct += 1;
498 } else {
499 eval.framework_violations.push(FrameworkViolation {
500 standard: if framework == "us_gaap" {
501 "ASC 842-10-25-2".to_string()
502 } else {
503 "IFRS 16.63".to_string()
504 },
505 description: format!(
506 "Lease {} classification may be incorrect",
507 lease.lease_id
508 ),
509 severity: ViolationSeverity::Warning,
510 });
511 }
512
513 if lease.check_rou_equals_liability() {
515 rou_accurate += 1;
516 }
517
518 if lease.discount_rate < 0.02 || lease.discount_rate > 0.15 {
520 eval.framework_violations.push(FrameworkViolation {
521 standard: "ASC 842-20-30-3".to_string(),
522 description: format!(
523 "Lease {} discount rate {:.2}% unusual",
524 lease.lease_id,
525 lease.discount_rate * 100.0
526 ),
527 severity: ViolationSeverity::Warning,
528 });
529 }
530 }
531
532 eval.classification_accuracy = classification_correct as f64 / leases.len() as f64;
533 eval.rou_asset_accuracy = rou_accurate as f64 / leases.len() as f64;
534 eval.lease_liability_accuracy = eval.rou_asset_accuracy; eval
537 }
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct FairValueEvaluation {
547 pub total_measurements: usize,
549 pub level_1_count: usize,
551 pub level_2_count: usize,
553 pub level_3_count: usize,
555 pub hierarchy_compliance: f64,
557 pub technique_consistency: f64,
559 pub framework_violations: Vec<FrameworkViolation>,
561}
562
563impl FairValueEvaluation {
564 pub fn new() -> Self {
566 Self {
567 total_measurements: 0,
568 level_1_count: 0,
569 level_2_count: 0,
570 level_3_count: 0,
571 hierarchy_compliance: 1.0,
572 technique_consistency: 1.0,
573 framework_violations: Vec::new(),
574 }
575 }
576}
577
578impl Default for FairValueEvaluation {
579 fn default() -> Self {
580 Self::new()
581 }
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct ImpairmentEvaluation {
591 pub total_tests: usize,
593 pub triggered_properly: usize,
595 pub trigger_recognition_rate: f64,
597 pub valid_recoverable_amounts: usize,
599 pub impairment_losses: usize,
601 pub reversals: usize,
603 pub invalid_reversals: usize,
605 pub framework_violations: Vec<FrameworkViolation>,
607}
608
609impl ImpairmentEvaluation {
610 pub fn new() -> Self {
612 Self {
613 total_tests: 0,
614 triggered_properly: 0,
615 trigger_recognition_rate: 1.0,
616 valid_recoverable_amounts: 0,
617 impairment_losses: 0,
618 reversals: 0,
619 invalid_reversals: 0,
620 framework_violations: Vec::new(),
621 }
622 }
623}
624
625impl Default for ImpairmentEvaluation {
626 fn default() -> Self {
627 Self::new()
628 }
629}
630
631#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct IsaComplianceEvaluation {
638 pub standards_covered: HashSet<String>,
640 pub total_requirements: usize,
642 pub requirements_addressed: usize,
644 pub coverage_rate: f64,
646 pub procedures_mapped: usize,
648 pub procedures_unmapped: usize,
650 pub critical_gaps: Vec<String>,
652}
653
654impl IsaComplianceEvaluation {
655 pub fn new() -> Self {
657 Self {
658 standards_covered: HashSet::new(),
659 total_requirements: 0,
660 requirements_addressed: 0,
661 coverage_rate: 1.0,
662 procedures_mapped: 0,
663 procedures_unmapped: 0,
664 critical_gaps: Vec::new(),
665 }
666 }
667}
668
669impl Default for IsaComplianceEvaluation {
670 fn default() -> Self {
671 Self::new()
672 }
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize)]
681pub struct SoxComplianceEvaluation {
682 pub section_302_certifications: usize,
684 pub section_404_assessments: usize,
686 pub key_controls: usize,
688 pub controls_tested: usize,
690 pub control_coverage: f64,
692 pub material_weaknesses: usize,
694 pub significant_deficiencies: usize,
696 pub valid_classifications: usize,
698}
699
700impl SoxComplianceEvaluation {
701 pub fn new() -> Self {
703 Self {
704 section_302_certifications: 0,
705 section_404_assessments: 0,
706 key_controls: 0,
707 controls_tested: 0,
708 control_coverage: 1.0,
709 material_weaknesses: 0,
710 significant_deficiencies: 0,
711 valid_classifications: 0,
712 }
713 }
714}
715
716impl Default for SoxComplianceEvaluation {
717 fn default() -> Self {
718 Self::new()
719 }
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
728pub struct PcaobComplianceEvaluation {
729 pub standards_covered: HashSet<String>,
731 pub coverage_rate: f64,
733 pub icfr_opinion_present: bool,
735 pub critical_audit_matters: usize,
737}
738
739impl PcaobComplianceEvaluation {
740 pub fn new() -> Self {
742 Self {
743 standards_covered: HashSet::new(),
744 coverage_rate: 1.0,
745 icfr_opinion_present: false,
746 critical_audit_matters: 0,
747 }
748 }
749}
750
751impl Default for PcaobComplianceEvaluation {
752 fn default() -> Self {
753 Self::new()
754 }
755}
756
757#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct AuditTrailEvaluation {
764 pub total_items: usize,
766 pub complete_items: usize,
768 pub completeness: f64,
770 pub risk_linked: usize,
772 pub evidence_linked: usize,
774 pub concluded: usize,
776 pub gaps: Vec<AuditTrailGap>,
778}
779
780impl AuditTrailEvaluation {
781 pub fn new() -> Self {
783 Self {
784 total_items: 0,
785 complete_items: 0,
786 completeness: 1.0,
787 risk_linked: 0,
788 evidence_linked: 0,
789 concluded: 0,
790 gaps: Vec::new(),
791 }
792 }
793}
794
795impl Default for AuditTrailEvaluation {
796 fn default() -> Self {
797 Self::new()
798 }
799}
800
801#[derive(Debug, Clone, Serialize, Deserialize)]
803pub struct AuditTrailGap {
804 pub trail_id: String,
806 pub gap_type: String,
808 pub description: String,
810}
811
812#[derive(Debug, Clone, Serialize, Deserialize)]
818pub struct FrameworkViolation {
819 pub standard: String,
821 pub description: String,
823 pub severity: ViolationSeverity,
825}
826
827#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
829pub enum ViolationSeverity {
830 Info,
832 Warning,
834 Error,
836 Critical,
838}
839
840#[derive(Debug, Clone, Serialize, Deserialize)]
846pub struct StandardsThresholds {
847 pub min_po_allocation_compliance: f64,
849 pub min_revenue_timing_compliance: f64,
851 pub min_lease_classification_accuracy: f64,
853 pub min_rou_asset_accuracy: f64,
855 pub min_fair_value_hierarchy_compliance: f64,
857 pub min_impairment_trigger_rate: f64,
859 pub min_isa_coverage: f64,
861 pub min_sox_control_coverage: f64,
863 pub min_audit_trail_completeness: f64,
865}
866
867impl Default for StandardsThresholds {
868 fn default() -> Self {
869 Self {
870 min_po_allocation_compliance: 0.95,
871 min_revenue_timing_compliance: 0.95,
872 min_lease_classification_accuracy: 0.90,
873 min_rou_asset_accuracy: 0.95,
874 min_fair_value_hierarchy_compliance: 0.95,
875 min_impairment_trigger_rate: 0.80,
876 min_isa_coverage: 0.90,
877 min_sox_control_coverage: 0.95,
878 min_audit_trail_completeness: 0.90,
879 }
880 }
881}
882
883#[cfg(test)]
884mod tests {
885 use super::*;
886
887 #[test]
888 fn test_standards_compliance_evaluation_new() {
889 let eval = StandardsComplianceEvaluation::new();
890 assert!(eval.passes);
891 assert!(eval.failures.is_empty());
892 }
893
894 #[test]
895 fn test_revenue_contract_validation() {
896 let contract = RevenueContract {
897 contract_id: "C001".to_string(),
898 transaction_price: dec!(10000),
899 performance_obligations: vec![
900 PerformanceObligation {
901 obligation_id: "PO1".to_string(),
902 allocated_amount: dec!(6000),
903 satisfaction_percent: dec!(1.0),
904 recognized_revenue: dec!(6000),
905 over_time: false,
906 },
907 PerformanceObligation {
908 obligation_id: "PO2".to_string(),
909 allocated_amount: dec!(4000),
910 satisfaction_percent: dec!(0.5),
911 recognized_revenue: dec!(2000),
912 over_time: true,
913 },
914 ],
915 variable_consideration: None,
916 revenue_recognized: dec!(8000),
917 deferred_revenue: dec!(2000),
918 };
919
920 assert!(contract.is_valid());
921 assert!(contract.check_po_allocation());
922 assert!(contract.check_revenue_timing());
923 assert!(contract.check_balance());
924 }
925
926 #[test]
927 fn test_revenue_contract_invalid_po_allocation() {
928 let contract = RevenueContract {
929 contract_id: "C002".to_string(),
930 transaction_price: dec!(10000),
931 performance_obligations: vec![PerformanceObligation {
932 obligation_id: "PO1".to_string(),
933 allocated_amount: dec!(5000), satisfaction_percent: dec!(1.0),
935 recognized_revenue: dec!(5000),
936 over_time: false,
937 }],
938 variable_consideration: None,
939 revenue_recognized: dec!(5000),
940 deferred_revenue: dec!(5000),
941 };
942
943 assert!(!contract.check_po_allocation());
944 }
945
946 #[test]
947 fn test_lease_us_gaap_classification() {
948 let finance_lease = LeaseEvaluation {
950 lease_id: "L001".to_string(),
951 lease_term_months: 48,
952 asset_useful_life_months: 60,
953 pv_ratio: 0.85,
954 is_finance: true,
955 framework: "us_gaap".to_string(),
956 rou_asset_initial: dec!(50000),
957 lease_liability_initial: dec!(50000),
958 discount_rate: 0.05,
959 };
960 assert!(finance_lease.check_us_gaap_classification());
961
962 let operating_lease = LeaseEvaluation {
964 lease_id: "L002".to_string(),
965 lease_term_months: 24,
966 asset_useful_life_months: 120,
967 pv_ratio: 0.50,
968 is_finance: false,
969 framework: "us_gaap".to_string(),
970 rou_asset_initial: dec!(20000),
971 lease_liability_initial: dec!(20000),
972 discount_rate: 0.05,
973 };
974 assert!(operating_lease.check_us_gaap_classification());
975 }
976
977 #[test]
978 fn test_standards_thresholds_check() {
979 let mut eval = StandardsComplianceEvaluation::new();
980 eval.revenue_recognition = Some(RevenueRecognitionEvaluation {
981 po_allocation_compliance: 0.90, timing_compliance: 0.98,
983 ..Default::default()
984 });
985
986 let thresholds = StandardsThresholds::default();
987 eval.check_thresholds(&thresholds);
988
989 assert!(!eval.passes);
990 assert_eq!(eval.failures.len(), 1);
991 assert!(eval.failures[0].contains("PO allocation"));
992 }
993
994 #[test]
995 fn test_lease_accounting_evaluator() {
996 let leases = vec![
997 LeaseEvaluation {
998 lease_id: "L001".to_string(),
999 lease_term_months: 48,
1000 asset_useful_life_months: 60,
1001 pv_ratio: 0.85,
1002 is_finance: true,
1003 framework: "us_gaap".to_string(),
1004 rou_asset_initial: dec!(50000),
1005 lease_liability_initial: dec!(50000),
1006 discount_rate: 0.05,
1007 },
1008 LeaseEvaluation {
1009 lease_id: "L002".to_string(),
1010 lease_term_months: 24,
1011 asset_useful_life_months: 120,
1012 pv_ratio: 0.50,
1013 is_finance: false,
1014 framework: "us_gaap".to_string(),
1015 rou_asset_initial: dec!(20000),
1016 lease_liability_initial: dec!(20000),
1017 discount_rate: 0.05,
1018 },
1019 ];
1020
1021 let eval = LeaseAccountingEvaluator::evaluate(&leases, "us_gaap");
1022
1023 assert_eq!(eval.total_leases, 2);
1024 assert_eq!(eval.finance_leases, 1);
1025 assert_eq!(eval.operating_leases, 1);
1026 assert_eq!(eval.classification_accuracy, 1.0);
1027 }
1028
1029 #[test]
1030 fn test_revenue_recognition_evaluator() {
1031 let contracts = vec![RevenueContract {
1032 contract_id: "C001".to_string(),
1033 transaction_price: dec!(10000),
1034 performance_obligations: vec![PerformanceObligation {
1035 obligation_id: "PO1".to_string(),
1036 allocated_amount: dec!(10000),
1037 satisfaction_percent: dec!(1.0),
1038 recognized_revenue: dec!(10000),
1039 over_time: false,
1040 }],
1041 variable_consideration: None,
1042 revenue_recognized: dec!(10000),
1043 deferred_revenue: dec!(0),
1044 }];
1045
1046 let eval = RevenueRecognitionEvaluator::evaluate(&contracts);
1047
1048 assert_eq!(eval.total_contracts, 1);
1049 assert_eq!(eval.valid_contracts, 1);
1050 assert_eq!(eval.po_allocation_compliance, 1.0);
1051 assert_eq!(eval.timing_compliance, 1.0);
1052 }
1053}