Skip to main content

datasynth_core/models/
external_expectation.rs

1//! External-expectation models — the ISA-520 substantive-analytics layer.
2//!
3//! An [`ExternalExpectation`] is a per-account *expected* period total derived from an exogenous
4//! driver (prior-year actuals, a market/industry index, a macro series, or the budget) together with
5//! a materiality tolerance band. It is the analytic input a substantive-analytics procedure compares
6//! the realized GL total against: a deviation beyond the band is the ISA-520 "investigate" trigger.
7//!
8//! These records are the engine-side realization of the perfect-crime countermeasure (Phase 2): a
9//! mimetic fraud preserves the per-entry ledger distribution and so evades the per-JE residual arms,
10//! but it still inflates an account's *aggregate*, which deviates from an expectation anchored to the
11//! account's *legitimate* level. Because the engine knows the generating process, each record also
12//! carries the ground truth — the fraud contribution to the actual — so the substantive arm can be
13//! scored against it (a band exceedance is a true positive iff the account was fraud-inflated).
14
15use rust_decimal::Decimal;
16use serde::{Deserialize, Serialize};
17
18use crate::models::chart_of_accounts::AccountType;
19
20/// The exogenous basis an expectation is built on (ISA 520 expectation bases).
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
22#[serde(rename_all = "snake_case")]
23pub enum ExpectationDriver {
24    /// Prior-period actual grown at an expected rate (trend analysis).
25    #[default]
26    PriorYear,
27    /// A market / industry index the account is expected to track.
28    MarketIndex,
29    /// A macroeconomic series (e.g. GDP, CPI) the account is expected to track.
30    MacroSeries,
31    /// The budgeted amount for the account.
32    Budget,
33}
34
35impl ExpectationDriver {
36    /// Human-readable label for the expectation basis (used in the record's `basis` text).
37    pub fn label(&self) -> &'static str {
38        match self {
39            ExpectationDriver::PriorYear => "prior-year actual",
40            ExpectationDriver::MarketIndex => "market/industry index",
41            ExpectationDriver::MacroSeries => "macroeconomic series",
42            ExpectationDriver::Budget => "budget",
43        }
44    }
45}
46
47/// An ISA-520 substantive-analytics expectation for a single GL account over a fiscal year.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ExternalExpectation {
50    /// Unique expectation identifier.
51    pub expectation_id: String,
52    /// Company the account belongs to.
53    pub company_code: String,
54    /// GL account number.
55    pub account_code: String,
56    /// GL account description.
57    pub account_description: String,
58    /// GL account type.
59    pub account_type: AccountType,
60    /// Fiscal year the expectation applies to.
61    pub fiscal_year: i32,
62    /// Exogenous driver the expectation is built on.
63    pub driver: ExpectationDriver,
64    /// Human-readable expectation basis (the ISA-520 methodology note).
65    pub basis: String,
66    /// The driver's value (e.g. the prior-year actual, or the index level).
67    #[serde(with = "crate::serde_decimal")]
68    pub driver_value: Decimal,
69    /// Expected period total for the account.
70    #[serde(with = "crate::serde_decimal")]
71    pub expected_value: Decimal,
72    /// Materiality tolerance band as a fraction of the expectation.
73    pub tolerance_pct: f64,
74    /// Lower bound of the acceptable band (`expected * (1 - tolerance_pct)`).
75    #[serde(with = "crate::serde_decimal")]
76    pub lower_bound: Decimal,
77    /// Upper bound of the acceptable band (`expected * (1 + tolerance_pct)`).
78    #[serde(with = "crate::serde_decimal")]
79    pub upper_bound: Decimal,
80    /// Realized GL total for the account (legitimate + any fraud).
81    #[serde(with = "crate::serde_decimal")]
82    pub actual_value: Decimal,
83    /// `actual_value - expected_value`.
84    #[serde(with = "crate::serde_decimal")]
85    pub deviation: Decimal,
86    /// Deviation in units of the tolerance band (a z-like score; |·| > 1 means out of band).
87    pub deviation_ratio: f64,
88    /// Whether the actual breaches the tolerance band — the ISA-520 "investigate" trigger.
89    pub exceeds_band: bool,
90    /// Ground truth: the fraud contribution to the actual (`actual - legitimate`).
91    #[serde(with = "crate::serde_decimal")]
92    pub fraud_inflation: Decimal,
93    /// Ground truth: whether the account received any fraud postings.
94    pub is_fraud_inflated: bool,
95}