Skip to main content

datasynth_core/models/compliance/
cross_reference.rs

1//! Cross-reference links between compliance standards.
2
3use serde::{Deserialize, Serialize};
4
5use super::standard_id::StandardId;
6
7/// Type of relationship between two standards.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum CrossReferenceType {
11    /// Standards are substantially converged (e.g., IFRS 15 ↔ ASC 606)
12    Converged,
13    /// Related but with significant differences (e.g., IFRS 16 ↔ ASC 842)
14    Related,
15    /// One standard complements the other
16    Complementary,
17    /// One standard is derived from or based on the other
18    DerivedFrom,
19    /// One standard has been incorporated into the other
20    IncorporatedInto,
21    /// Standards address the same domain from different angles
22    Parallel,
23    /// ISA ↔ PCAOB mapping
24    AuditMapping,
25    /// Control framework mapping (COSO ↔ SOX ↔ ISA)
26    ControlFrameworkMapping,
27}
28
29impl std::fmt::Display for CrossReferenceType {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::Converged => write!(f, "Converged"),
33            Self::Related => write!(f, "Related"),
34            Self::Complementary => write!(f, "Complementary"),
35            Self::DerivedFrom => write!(f, "Derived From"),
36            Self::IncorporatedInto => write!(f, "Incorporated Into"),
37            Self::Parallel => write!(f, "Parallel"),
38            Self::AuditMapping => write!(f, "Audit Mapping"),
39            Self::ControlFrameworkMapping => write!(f, "Control Framework Mapping"),
40        }
41    }
42}
43
44/// A cross-reference link between two standards.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct CrossReference {
47    /// Source standard
48    pub from_standard: StandardId,
49    /// Target standard
50    pub to_standard: StandardId,
51    /// Type of relationship
52    pub relationship: CrossReferenceType,
53    /// Convergence level (0.0 = no convergence, 1.0 = fully converged)
54    pub convergence_level: f64,
55    /// Description of the relationship
56    pub description: Option<String>,
57}
58
59impl CrossReference {
60    /// Creates a new cross-reference.
61    pub fn new(from: StandardId, to: StandardId, relationship: CrossReferenceType) -> Self {
62        let convergence = match relationship {
63            CrossReferenceType::Converged => 0.9,
64            CrossReferenceType::IncorporatedInto => 0.85,
65            CrossReferenceType::Related => 0.6,
66            CrossReferenceType::Complementary => 0.5,
67            CrossReferenceType::DerivedFrom => 0.7,
68            CrossReferenceType::Parallel => 0.4,
69            CrossReferenceType::AuditMapping => 0.7,
70            CrossReferenceType::ControlFrameworkMapping => 0.6,
71        };
72
73        Self {
74            from_standard: from,
75            to_standard: to,
76            relationship,
77            convergence_level: convergence,
78            description: None,
79        }
80    }
81
82    /// Sets the convergence level explicitly.
83    pub fn with_convergence(mut self, level: f64) -> Self {
84        self.convergence_level = level.clamp(0.0, 1.0);
85        self
86    }
87
88    /// Adds a description.
89    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
90        self.description = Some(desc.into());
91        self
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_cross_reference() {
101        let xref = CrossReference::new(
102            StandardId::new("IFRS", "15"),
103            StandardId::new("ASC", "606"),
104            CrossReferenceType::Converged,
105        )
106        .with_description("Joint standard — substantially converged");
107
108        assert_eq!(xref.from_standard.as_str(), "IFRS-15");
109        assert_eq!(xref.to_standard.as_str(), "ASC-606");
110        assert!(xref.convergence_level > 0.8);
111    }
112}