//! Kvitt domain rules as pure functions (SPEC §9.1, §9.3, §9.4, §9.5 `next_occurrence_date`, §14.5, and the //! server-side analytics rules of §14.5.7). //! //! No I/O, no clock, no randomness, no locale. Ids are plain strings (the test vectors use short ids like `"a"`); //! money is `i64` öre and basis points are `u32`. The Kotlin `:domain` module implements the same functions and both //! must pass every vector in `contracts/test-vectors/` (`tests/vectors.rs`). // No unwrap/expect outside tests (CLAUDE.md server rule); integration tests are separate crates and may use them. #![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))] mod allocation; mod analytics; mod balance; mod recurring; mod settlement; mod split; pub use allocation::{ AccountPosition, Allocation, AllocationError, AllocationProposal, over_settled_accounts, propose_allocation, }; pub use analytics::{ MOVING_AVERAGE_WINDOW, average, div_half_up, moving_average, occurs_in_period, percent_vs_average, }; pub use balance::{BalanceExpense, BalanceSettlement, Balances, compute_balances}; pub use recurring::{ Period, RecurrenceError, RecurrenceSchedule, next_candidate_period, next_occurrence_date, }; pub use settlement::{SettlementError, validate_settlement}; pub use split::{SplitError, SplitSpec, compute_split}; /// Smallest valid amount of an expense, settlement or template, in öre (SPEC §9.6). pub const AMOUNT_MIN_ORE: i64 = 1; /// Largest valid amount of an expense, settlement or template, in öre: 10 000 000,00 kr (SPEC §9.6). pub const AMOUNT_MAX_ORE: i64 = 1_000_000_000; /// 100 % in basis points. pub const BP_TOTAL: u32 = 10_000; /// Most allocations one settlement may have (SPEC §9.6). pub const MAX_ALLOCATIONS: usize = 50; /// Whether `amount_ore` is within SPEC §9.6 (1 to 1 000 000 000). pub fn amount_in_range(amount_ore: i64) -> bool { (AMOUNT_MIN_ORE..=AMOUNT_MAX_ORE).contains(&amount_ore) }