use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::cross_reference::CrossReference;
use super::standard_id::StandardId;
use super::temporal::TemporalVersion;
use crate::models::graph_properties::{GraphPropertyValue, ToNodeProperties};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StandardCategory {
AccountingStandard,
AuditingStandard,
RegulatoryRequirement,
ReportingStandard,
PrudentialRegulation,
TaxRegulation,
DataProtection,
SustainabilityStandard,
}
impl std::fmt::Display for StandardCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AccountingStandard => write!(f, "Accounting Standard"),
Self::AuditingStandard => write!(f, "Auditing Standard"),
Self::RegulatoryRequirement => write!(f, "Regulatory Requirement"),
Self::ReportingStandard => write!(f, "Reporting Standard"),
Self::PrudentialRegulation => write!(f, "Prudential Regulation"),
Self::TaxRegulation => write!(f, "Tax Regulation"),
Self::DataProtection => write!(f, "Data Protection"),
Self::SustainabilityStandard => write!(f, "Sustainability Standard"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ComplianceDomain {
FinancialReporting,
InternalControl,
ExternalAudit,
TaxCompliance,
RegulatoryReporting,
RiskManagement,
DataGovernance,
Sustainability,
AntiMoneyLaundering,
PrudentialCapital,
}
impl std::fmt::Display for ComplianceDomain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FinancialReporting => write!(f, "Financial Reporting"),
Self::InternalControl => write!(f, "Internal Control"),
Self::ExternalAudit => write!(f, "External Audit"),
Self::TaxCompliance => write!(f, "Tax Compliance"),
Self::RegulatoryReporting => write!(f, "Regulatory Reporting"),
Self::RiskManagement => write!(f, "Risk Management"),
Self::DataGovernance => write!(f, "Data Governance"),
Self::Sustainability => write!(f, "Sustainability"),
Self::AntiMoneyLaundering => write!(f, "Anti-Money Laundering"),
Self::PrudentialCapital => write!(f, "Prudential Capital"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IssuingBody {
Iasb,
Iaasb,
Fasb,
Pcaob,
Sec,
Bcbs,
EuropeanUnion,
Frc,
Drsc,
Idw,
Anc,
Asbj,
Icai,
Issb,
Oecd,
Custom(String),
}
impl std::fmt::Display for IssuingBody {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Iasb => write!(f, "IASB"),
Self::Iaasb => write!(f, "IAASB"),
Self::Fasb => write!(f, "FASB"),
Self::Pcaob => write!(f, "PCAOB"),
Self::Sec => write!(f, "SEC"),
Self::Bcbs => write!(f, "BCBS"),
Self::EuropeanUnion => write!(f, "EU"),
Self::Frc => write!(f, "FRC"),
Self::Drsc => write!(f, "DRSC"),
Self::Idw => write!(f, "IDW"),
Self::Anc => write!(f, "ANC"),
Self::Asbj => write!(f, "ASBJ"),
Self::Icai => write!(f, "ICAI"),
Self::Issb => write!(f, "ISSB"),
Self::Oecd => write!(f, "OECD"),
Self::Custom(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StandardRequirement {
pub id: String,
pub title: String,
pub description: String,
pub assertions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceStandard {
pub id: StandardId,
pub title: String,
pub issuing_body: IssuingBody,
pub category: StandardCategory,
pub domain: ComplianceDomain,
pub versions: Vec<TemporalVersion>,
pub supersedes: Vec<StandardId>,
pub superseded_by: Option<StandardId>,
pub cross_references: Vec<CrossReference>,
pub mandatory_jurisdictions: Vec<String>,
pub permitted_jurisdictions: Vec<String>,
pub requirements: Vec<StandardRequirement>,
#[serde(default)]
pub applicable_account_types: Vec<String>,
#[serde(default)]
pub applicable_processes: Vec<String>,
}
impl ComplianceStandard {
pub fn new(
id: StandardId,
title: impl Into<String>,
issuing_body: IssuingBody,
category: StandardCategory,
domain: ComplianceDomain,
) -> Self {
Self {
id,
title: title.into(),
issuing_body,
category,
domain,
versions: Vec::new(),
supersedes: Vec::new(),
superseded_by: None,
cross_references: Vec::new(),
mandatory_jurisdictions: Vec::new(),
permitted_jurisdictions: Vec::new(),
requirements: Vec::new(),
applicable_account_types: Vec::new(),
applicable_processes: Vec::new(),
}
}
pub fn with_version(mut self, version: TemporalVersion) -> Self {
self.versions.push(version);
self
}
pub fn supersedes_standard(mut self, id: StandardId) -> Self {
self.supersedes.push(id);
self
}
pub fn superseded_by_standard(mut self, id: StandardId) -> Self {
self.superseded_by = Some(id);
self
}
pub fn with_cross_reference(mut self, xref: CrossReference) -> Self {
self.cross_references.push(xref);
self
}
pub fn mandatory_in(mut self, country_code: &str) -> Self {
self.mandatory_jurisdictions.push(country_code.to_string());
self
}
pub fn with_requirement(mut self, req: StandardRequirement) -> Self {
self.requirements.push(req);
self
}
pub fn with_account_type(mut self, account_type: &str) -> Self {
self.applicable_account_types.push(account_type.to_string());
self
}
pub fn with_account_types(mut self, types: &[&str]) -> Self {
self.applicable_account_types
.extend(types.iter().map(|s| s.to_string()));
self
}
pub fn with_process(mut self, process: &str) -> Self {
self.applicable_processes.push(process.to_string());
self
}
pub fn with_processes(mut self, processes: &[&str]) -> Self {
self.applicable_processes
.extend(processes.iter().map(|s| s.to_string()));
self
}
pub fn is_superseded(&self) -> bool {
self.superseded_by.is_some()
}
}
impl ToNodeProperties for ComplianceStandard {
fn node_type_name(&self) -> &'static str {
"compliance_standard"
}
fn node_type_code(&self) -> u16 {
510
}
fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
let mut p = HashMap::new();
p.insert(
"standardId".into(),
GraphPropertyValue::String(self.id.as_str().to_string()),
);
p.insert(
"title".into(),
GraphPropertyValue::String(self.title.clone()),
);
p.insert(
"issuingBody".into(),
GraphPropertyValue::String(self.issuing_body.to_string()),
);
p.insert(
"category".into(),
GraphPropertyValue::String(self.category.to_string()),
);
p.insert(
"domain".into(),
GraphPropertyValue::String(self.domain.to_string()),
);
p.insert(
"isSuperseded".into(),
GraphPropertyValue::Bool(self.is_superseded()),
);
p.insert(
"mandatoryJurisdictions".into(),
GraphPropertyValue::StringList(self.mandatory_jurisdictions.clone()),
);
if !self.applicable_account_types.is_empty() {
p.insert(
"applicableAccountTypes".into(),
GraphPropertyValue::StringList(self.applicable_account_types.clone()),
);
}
if !self.applicable_processes.is_empty() {
p.insert(
"applicableProcesses".into(),
GraphPropertyValue::StringList(self.applicable_processes.clone()),
);
}
p.insert(
"requirementCount".into(),
GraphPropertyValue::Int(self.requirements.len() as i64),
);
p.insert(
"versionCount".into(),
GraphPropertyValue::Int(self.versions.len() as i64),
);
p
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compliance_standard_creation() {
let std = ComplianceStandard::new(
StandardId::new("IFRS", "16"),
"Leases",
IssuingBody::Iasb,
StandardCategory::AccountingStandard,
ComplianceDomain::FinancialReporting,
)
.mandatory_in("DE")
.mandatory_in("GB");
assert_eq!(std.id.as_str(), "IFRS-16");
assert_eq!(std.mandatory_jurisdictions.len(), 2);
assert!(!std.is_superseded());
}
}