use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EstimateType {
DeferredTaxProvision,
ExpectedCreditLoss,
PensionObligation,
FairValueMeasurement,
ImpairmentTest,
ProvisionForLiabilities,
ShareBasedPayment,
DepreciationUsefulLife,
}
impl std::fmt::Display for EstimateType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::DeferredTaxProvision => "Deferred Tax Provision",
Self::ExpectedCreditLoss => "Expected Credit Loss",
Self::PensionObligation => "Pension Obligation",
Self::FairValueMeasurement => "Fair Value Measurement",
Self::ImpairmentTest => "Impairment Test",
Self::ProvisionForLiabilities => "Provision for Liabilities",
Self::ShareBasedPayment => "Share-Based Payment",
Self::DepreciationUsefulLife => "Depreciation Useful Life",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UncertaintyLevel {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EstimateComplexity {
Simple,
Moderate,
Complex,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AssumptionAssessment {
Reasonable,
Optimistic,
Aggressive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SubjectivityLevel {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EstimateAssumption {
pub description: String,
#[serde(with = "crate::serde_decimal")]
pub sensitivity: Decimal,
pub reasonableness: AssumptionAssessment,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Isa540RiskFactors {
pub estimation_uncertainty: UncertaintyLevel,
pub complexity: EstimateComplexity,
pub subjectivity: SubjectivityLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetrospectiveReview {
#[serde(with = "crate::serde_decimal")]
pub prior_period_estimate: Decimal,
#[serde(with = "crate::serde_decimal")]
pub actual_outcome: Decimal,
#[serde(with = "crate::serde_decimal")]
pub variance: Decimal,
#[serde(with = "crate::serde_decimal")]
pub variance_percentage: Decimal,
pub management_bias_indicator: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountingEstimate {
pub id: String,
pub entity_code: String,
pub estimate_type: EstimateType,
pub description: String,
#[serde(with = "crate::serde_decimal")]
pub management_point_estimate: Decimal,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "crate::serde_decimal::option"
)]
pub auditor_point_estimate: Option<Decimal>,
pub estimation_uncertainty: UncertaintyLevel,
pub complexity: EstimateComplexity,
pub assumptions: Vec<EstimateAssumption>,
pub retrospective_review: Option<RetrospectiveReview>,
pub isa540_risk_factors: Isa540RiskFactors,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_estimate_type_display() {
assert_eq!(
EstimateType::ExpectedCreditLoss.to_string(),
"Expected Credit Loss"
);
assert_eq!(
EstimateType::PensionObligation.to_string(),
"Pension Obligation"
);
}
#[test]
fn test_accounting_estimate_roundtrip() {
let estimate = AccountingEstimate {
id: "EST-001".to_string(),
entity_code: "C001".to_string(),
estimate_type: EstimateType::ExpectedCreditLoss,
description: "ECL allowance — trade receivables".to_string(),
management_point_estimate: dec!(125000.00),
auditor_point_estimate: Some(dec!(130000.00)),
estimation_uncertainty: UncertaintyLevel::High,
complexity: EstimateComplexity::Moderate,
assumptions: vec![EstimateAssumption {
description: "12-month default rate 2.5%".to_string(),
sensitivity: dec!(50000.00),
reasonableness: AssumptionAssessment::Reasonable,
}],
retrospective_review: Some(RetrospectiveReview {
prior_period_estimate: dec!(115000.00),
actual_outcome: dec!(118000.00),
variance: dec!(3000.00),
variance_percentage: dec!(2.61),
management_bias_indicator: false,
}),
isa540_risk_factors: Isa540RiskFactors {
estimation_uncertainty: UncertaintyLevel::High,
complexity: EstimateComplexity::Moderate,
subjectivity: SubjectivityLevel::Medium,
},
};
let json = serde_json::to_string(&estimate).unwrap();
let parsed: AccountingEstimate = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.entity_code, "C001");
assert_eq!(parsed.estimate_type, EstimateType::ExpectedCreditLoss);
assert!(parsed.auditor_point_estimate.is_some());
}
}