datasynth_generators/industry/
common.rs1use chrono::NaiveDate;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8pub trait IndustryTransaction: std::fmt::Debug + Send + Sync {
10 fn transaction_type(&self) -> &str;
12
13 fn date(&self) -> NaiveDate;
15
16 fn amount(&self) -> Option<Decimal>;
18
19 fn accounts(&self) -> Vec<String>;
21
22 fn to_journal_lines(&self) -> Vec<IndustryJournalLine>;
24
25 fn metadata(&self) -> HashMap<String, String>;
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct IndustryJournalLine {
32 pub account: String,
34 pub debit: Decimal,
36 pub credit: Decimal,
38 pub description: String,
40 pub cost_center: Option<String>,
42 pub dimensions: HashMap<String, String>,
44}
45
46impl IndustryJournalLine {
47 pub fn debit(
49 account: impl Into<String>,
50 amount: Decimal,
51 description: impl Into<String>,
52 ) -> Self {
53 Self {
54 account: account.into(),
55 debit: amount,
56 credit: Decimal::ZERO,
57 description: description.into(),
58 cost_center: None,
59 dimensions: HashMap::new(),
60 }
61 }
62
63 pub fn credit(
65 account: impl Into<String>,
66 amount: Decimal,
67 description: impl Into<String>,
68 ) -> Self {
69 Self {
70 account: account.into(),
71 debit: Decimal::ZERO,
72 credit: amount,
73 description: description.into(),
74 cost_center: None,
75 dimensions: HashMap::new(),
76 }
77 }
78
79 pub fn with_cost_center(mut self, cost_center: impl Into<String>) -> Self {
81 self.cost_center = Some(cost_center.into());
82 self
83 }
84
85 pub fn with_dimension(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
87 self.dimensions.insert(key.into(), value.into());
88 self
89 }
90}
91
92pub trait IndustryAnomaly: std::fmt::Debug + Send + Sync {
94 fn anomaly_type(&self) -> &str;
96
97 fn severity(&self) -> u8;
99
100 fn detection_difficulty(&self) -> &str;
102
103 fn indicators(&self) -> Vec<String>;
105
106 fn regulatory_concerns(&self) -> Vec<String>;
108}
109
110#[allow(unused)]
115pub trait IndustryTransactionGenerator: Send + Sync {
116 type Transaction: IndustryTransaction;
118
119 type Anomaly: IndustryAnomaly;
121
122 fn generate_transactions(
124 &self,
125 start_date: NaiveDate,
126 end_date: NaiveDate,
127 count: usize,
128 ) -> Vec<Self::Transaction>;
129
130 fn generate_anomalies(&self, transactions: &[Self::Transaction]) -> Vec<Self::Anomaly>;
132
133 fn gl_accounts(&self) -> Vec<IndustryGlAccount>;
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct IndustryGlAccount {
140 pub account_number: String,
142 pub name: String,
144 pub account_type: String,
146 pub category: String,
148 pub is_control: bool,
150 pub normal_balance: String,
152}
153
154impl IndustryGlAccount {
155 pub fn new(
157 number: impl Into<String>,
158 name: impl Into<String>,
159 account_type: impl Into<String>,
160 category: impl Into<String>,
161 ) -> Self {
162 Self {
163 account_number: number.into(),
164 name: name.into(),
165 account_type: account_type.into(),
166 category: category.into(),
167 is_control: false,
168 normal_balance: "Debit".to_string(),
169 }
170 }
171
172 pub fn into_control(mut self) -> Self {
174 self.is_control = true;
175 self
176 }
177
178 pub fn with_normal_balance(mut self, balance: impl Into<String>) -> Self {
180 self.normal_balance = balance.into();
181 self
182 }
183}
184
185#[cfg(test)]
186#[allow(clippy::unwrap_used)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_journal_line() {
192 let debit = IndustryJournalLine::debit("1000", Decimal::new(1000, 0), "Test debit")
193 .with_cost_center("CC001")
194 .with_dimension("project", "P001");
195
196 assert_eq!(debit.account, "1000");
197 assert_eq!(debit.debit, Decimal::new(1000, 0));
198 assert_eq!(debit.credit, Decimal::ZERO);
199 assert_eq!(debit.cost_center, Some("CC001".to_string()));
200 assert_eq!(debit.dimensions.get("project"), Some(&"P001".to_string()));
201
202 let credit = IndustryJournalLine::credit("2000", Decimal::new(1000, 0), "Test credit");
203 assert_eq!(credit.debit, Decimal::ZERO);
204 assert_eq!(credit.credit, Decimal::new(1000, 0));
205 }
206
207 #[test]
208 fn test_gl_account() {
209 let account =
210 IndustryGlAccount::new("5100", "Cost of Goods Sold", "Expense", "Manufacturing")
211 .into_control()
212 .with_normal_balance("Debit");
213
214 assert_eq!(account.account_number, "5100");
215 assert!(account.is_control);
216 assert_eq!(account.normal_balance, "Debit");
217 }
218}