///|
pub(all) enum AnomalyKind {
LargeDebit
LargeCredit
UnmappedAccount
InvalidPeriod
DuplicateEntry
} derive(Debug, Eq)
///|
pub struct Anomaly {
kind : AnomalyKind
account : String
period : String
amount : Amount
detail : String
} derive(Debug, Eq)
///|
pub fn detect_large_entries(
entries : Array[Entry],
threshold : Amount,
) -> Array[Anomaly] {
let result : Array[Anomaly] = []
for entry in entries {
let debit = if entry.debit < 0 { -entry.debit } else { entry.debit }
let credit = if entry.credit < 0 { -entry.credit } else { entry.credit }
if debit >= threshold {
result.push({
kind: LargeDebit,
account: entry.account,
period: entry.period,
amount: entry.debit,
detail: "debit exceeds threshold",
})
}
if credit >= threshold {
result.push({
kind: LargeCredit,
account: entry.account,
period: entry.period,
amount: entry.credit,
detail: "credit exceeds threshold",
})
}
}
result
}
///|
pub fn detect_unmapped(
entries : Array[Entry],
mappings : Array[AccountMapping],
) -> Array[Anomaly] {
let result : Array[Anomaly] = []
for entry in entries {
if find_mapping(mappings, entry.account) is None {
result.push({
kind: UnmappedAccount,
account: entry.account,
period: entry.period,
amount: entry.debit - entry.credit,
detail: "account has no statement mapping",
})
}
}
result
}
///|
pub fn detect_invalid_periods(entries : Array[Entry]) -> Array[Anomaly] {
let result : Array[Anomaly] = []
for entry in entries {
if validate_period(entry.period) is InvalidPeriod(_) {
result.push({
kind: InvalidPeriod,
account: entry.account,
period: entry.period,
amount: entry.debit - entry.credit,
detail: "period must use YYYY-Qn",
})
}
}
result
}