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}