Skip to main content

ringkernel_accnet/models/
mod.rs

1//! Core data models for accounting network analytics.
2//!
3//! These models represent the fundamental structures of double-entry bookkeeping
4//! transformed into a graph representation for GPU-accelerated analysis.
5
6mod account;
7mod flow;
8mod journal;
9mod network;
10mod patterns;
11mod temporal;
12
13pub use account::*;
14pub use flow::*;
15pub use journal::*;
16pub use network::*;
17pub use patterns::*;
18pub use temporal::*;
19
20use rkyv::{Archive, Deserialize, Serialize};
21
22/// Decimal128 representation for precise monetary amounts.
23/// Uses fixed-point arithmetic: value = mantissa * 10^(-scale)
24#[derive(Debug, Clone, Copy, PartialEq, Archive, Serialize, Deserialize)]
25#[archive(compare(PartialEq))]
26#[repr(C)]
27pub struct Decimal128 {
28    /// The mantissa (integer part when scale=0)
29    pub mantissa: i128,
30    /// Decimal places (typically 2 for currency)
31    pub scale: u8,
32}
33
34impl Decimal128 {
35    /// Zero value constant with scale 2.
36    pub const ZERO: Self = Self {
37        mantissa: 0,
38        scale: 2,
39    };
40
41    /// Create a new Decimal128 with given mantissa and scale.
42    pub fn new(mantissa: i128, scale: u8) -> Self {
43        Self { mantissa, scale }
44    }
45
46    /// Create a zero value.
47    pub fn zero() -> Self {
48        Self::ZERO
49    }
50
51    /// Create from cents (scale 2).
52    pub fn from_cents(cents: i64) -> Self {
53        Self {
54            mantissa: cents as i128,
55            scale: 2,
56        }
57    }
58
59    /// Create from f64 (scale 2).
60    pub fn from_f64(value: f64) -> Self {
61        Self {
62            mantissa: (value * 100.0).round() as i128,
63            scale: 2,
64        }
65    }
66
67    /// Convert to f64.
68    pub fn to_f64(&self) -> f64 {
69        self.mantissa as f64 / 10f64.powi(self.scale as i32)
70    }
71
72    /// Get absolute value.
73    pub fn abs(&self) -> Self {
74        Self {
75            mantissa: self.mantissa.abs(),
76            scale: self.scale,
77        }
78    }
79
80    /// Check if value is zero.
81    pub fn is_zero(&self) -> bool {
82        self.mantissa == 0
83    }
84
85    /// Check if value is positive.
86    pub fn is_positive(&self) -> bool {
87        self.mantissa > 0
88    }
89
90    /// Check if value is negative.
91    pub fn is_negative(&self) -> bool {
92        self.mantissa < 0
93    }
94}
95
96impl std::ops::Add for Decimal128 {
97    type Output = Self;
98    fn add(self, rhs: Self) -> Self {
99        // Assume same scale for simplicity
100        Self {
101            mantissa: self.mantissa + rhs.mantissa,
102            scale: self.scale,
103        }
104    }
105}
106
107impl std::ops::Sub for Decimal128 {
108    type Output = Self;
109    fn sub(self, rhs: Self) -> Self {
110        Self {
111            mantissa: self.mantissa - rhs.mantissa,
112            scale: self.scale,
113        }
114    }
115}
116
117impl std::ops::Neg for Decimal128 {
118    type Output = Self;
119    fn neg(self) -> Self {
120        Self {
121            mantissa: -self.mantissa,
122            scale: self.scale,
123        }
124    }
125}
126
127impl Default for Decimal128 {
128    fn default() -> Self {
129        Self::ZERO
130    }
131}
132
133impl std::fmt::Display for Decimal128 {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        let value = self.to_f64();
136        write!(f, "${:.2}", value)
137    }
138}
139
140/// Hybrid Logical Timestamp for causal ordering.
141/// Combines physical time with logical counter.
142#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Archive, Serialize, Deserialize)]
143#[archive(compare(PartialEq))]
144#[repr(C)]
145pub struct HybridTimestamp {
146    /// Physical time component (milliseconds since epoch)
147    pub physical: u64,
148    /// Logical counter for same-millisecond ordering
149    pub logical: u32,
150    /// Node ID for tie-breaking
151    pub node_id: u32,
152}
153
154impl HybridTimestamp {
155    /// Create a timestamp with current physical time.
156    pub fn now() -> Self {
157        use std::time::{SystemTime, UNIX_EPOCH};
158        let physical = SystemTime::now()
159            .duration_since(UNIX_EPOCH)
160            .unwrap()
161            .as_millis() as u64;
162        Self {
163            physical,
164            logical: 0,
165            node_id: 0,
166        }
167    }
168
169    /// Create a timestamp with current time and specified node ID.
170    pub fn with_node_id(node_id: u32) -> Self {
171        use std::time::{SystemTime, UNIX_EPOCH};
172        let physical = SystemTime::now()
173            .duration_since(UNIX_EPOCH)
174            .unwrap()
175            .as_millis() as u64;
176        Self {
177            physical,
178            logical: 0,
179            node_id,
180        }
181    }
182
183    /// Create a timestamp with specific physical and logical components.
184    pub fn new(physical: u64, logical: u32) -> Self {
185        Self {
186            physical,
187            logical,
188            node_id: 0,
189        }
190    }
191
192    /// Create a zero timestamp.
193    pub fn zero() -> Self {
194        Self {
195            physical: 0,
196            logical: 0,
197            node_id: 0,
198        }
199    }
200}
201
202impl Default for HybridTimestamp {
203    fn default() -> Self {
204        Self::zero()
205    }
206}