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