Skip to main content

stateset_core/models/
analytics.rs

1//! Analytics and reporting models
2//!
3//! Provides types for sales analytics, inventory forecasting, and business intelligence.
4
5use chrono::{DateTime, Utc};
6use rust_decimal::Decimal;
7use serde::{Deserialize, Serialize};
8use stateset_primitives::ProductId;
9use uuid::Uuid;
10
11// ============================================================================
12// Time Period
13// ============================================================================
14
15/// Time period for analytics queries
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18#[non_exhaustive]
19pub enum TimePeriod {
20    Today,
21    Yesterday,
22    Last7Days,
23    Last30Days,
24    ThisMonth,
25    LastMonth,
26    ThisQuarter,
27    LastQuarter,
28    ThisYear,
29    LastYear,
30    AllTime,
31    Custom,
32}
33
34impl Default for TimePeriod {
35    fn default() -> Self {
36        Self::Last30Days
37    }
38}
39
40/// Date range for custom period queries
41#[derive(Debug, Clone, Default, Serialize, Deserialize)]
42pub struct DateRange {
43    pub start: Option<DateTime<Utc>>,
44    pub end: Option<DateTime<Utc>>,
45}
46
47// ============================================================================
48// Sales Analytics
49// ============================================================================
50
51/// Sales summary metrics
52#[derive(Debug, Clone, Default, Serialize, Deserialize)]
53pub struct SalesSummary {
54    /// Total revenue in the period
55    pub total_revenue: Decimal,
56    /// Number of orders
57    pub order_count: u64,
58    /// Average order value
59    pub average_order_value: Decimal,
60    /// Number of items sold
61    pub items_sold: u64,
62    /// Number of unique customers
63    pub unique_customers: u64,
64    /// Revenue change vs previous period (percentage)
65    pub revenue_change_percent: Option<Decimal>,
66    /// Order count change vs previous period (percentage)
67    pub order_count_change_percent: Option<Decimal>,
68    /// Period start
69    pub period_start: Option<DateTime<Utc>>,
70    /// Period end
71    pub period_end: Option<DateTime<Utc>>,
72}
73
74/// Revenue breakdown by time bucket
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct RevenueByPeriod {
77    /// Time bucket label (e.g., "2024-01-15", "Week 3", "January")
78    pub period: String,
79    /// Revenue for this period
80    pub revenue: Decimal,
81    /// Order count for this period
82    pub order_count: u64,
83    /// Start of period
84    pub period_start: DateTime<Utc>,
85}
86
87/// Granularity for time-series data
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90#[non_exhaustive]
91pub enum TimeGranularity {
92    Hour,
93    Day,
94    Week,
95    Month,
96    Quarter,
97    Year,
98}
99
100impl Default for TimeGranularity {
101    fn default() -> Self {
102        Self::Day
103    }
104}
105
106// ============================================================================
107// Product Analytics
108// ============================================================================
109
110/// Top selling product
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct TopProduct {
113    /// Product ID
114    pub product_id: Option<ProductId>,
115    /// SKU
116    pub sku: String,
117    /// Product name
118    pub name: String,
119    /// Total units sold
120    pub units_sold: u64,
121    /// Total revenue from this product
122    pub revenue: Decimal,
123    /// Number of orders containing this product
124    pub order_count: u64,
125    /// Average selling price
126    pub average_price: Decimal,
127}
128
129/// Product performance metrics
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ProductPerformance {
132    pub product_id: ProductId,
133    pub sku: String,
134    pub name: String,
135    /// Units sold in current period
136    pub units_sold: u64,
137    /// Revenue in current period
138    pub revenue: Decimal,
139    /// Units sold in previous period
140    pub previous_units_sold: u64,
141    /// Revenue in previous period
142    pub previous_revenue: Decimal,
143    /// Growth rate (units)
144    pub units_growth_percent: Decimal,
145    /// Growth rate (revenue)
146    pub revenue_growth_percent: Decimal,
147}
148
149// ============================================================================
150// Customer Analytics
151// ============================================================================
152
153/// Customer segment metrics
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CustomerMetrics {
156    /// Total customers
157    pub total_customers: u64,
158    /// New customers in period
159    pub new_customers: u64,
160    /// Returning customers (ordered more than once)
161    pub returning_customers: u64,
162    /// Average customer lifetime value
163    pub average_lifetime_value: Decimal,
164    /// Average orders per customer
165    pub average_orders_per_customer: Decimal,
166    /// Customer retention rate
167    pub retention_rate_percent: Option<Decimal>,
168}
169
170/// Top customer by spend
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct TopCustomer {
173    pub customer_id: Uuid,
174    pub email: String,
175    pub name: String,
176    /// Total spend
177    pub total_spent: Decimal,
178    /// Number of orders
179    pub order_count: u64,
180    /// Average order value
181    pub average_order_value: Decimal,
182    /// First order date
183    pub first_order_date: Option<DateTime<Utc>>,
184    /// Last order date
185    pub last_order_date: Option<DateTime<Utc>>,
186}
187
188// ============================================================================
189// Inventory Analytics
190// ============================================================================
191
192/// Inventory health summary
193#[derive(Debug, Clone, Default, Serialize, Deserialize)]
194pub struct InventoryHealth {
195    /// Total SKUs tracked
196    pub total_skus: u64,
197    /// SKUs currently in stock
198    pub in_stock_skus: u64,
199    /// SKUs at low stock level
200    pub low_stock_skus: u64,
201    /// SKUs out of stock
202    pub out_of_stock_skus: u64,
203    /// Total inventory value
204    pub total_value: Decimal,
205    /// Inventory turnover ratio
206    pub turnover_ratio: Option<Decimal>,
207}
208
209/// Low stock alert
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct LowStockItem {
212    pub sku: String,
213    pub name: String,
214    /// Current on-hand quantity
215    pub on_hand: Decimal,
216    /// Allocated (reserved) quantity
217    pub allocated: Decimal,
218    /// Available quantity
219    pub available: Decimal,
220    /// Reorder point threshold
221    pub reorder_point: Option<Decimal>,
222    /// Average daily sales
223    pub average_daily_sales: Option<Decimal>,
224    /// Days of stock remaining
225    pub days_of_stock: Option<Decimal>,
226}
227
228/// Inventory movement summary
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct InventoryMovement {
231    pub sku: String,
232    pub name: String,
233    /// Units sold
234    pub units_sold: u64,
235    /// Units received (from POs)
236    pub units_received: u64,
237    /// Units returned
238    pub units_returned: u64,
239    /// Units adjusted (manual)
240    pub units_adjusted: i64,
241    /// Net change
242    pub net_change: i64,
243}
244
245// ============================================================================
246// Forecasting
247// ============================================================================
248
249/// Demand forecast for a SKU
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct DemandForecast {
252    pub sku: String,
253    pub name: String,
254    /// Historical average daily demand
255    pub average_daily_demand: Decimal,
256    /// Forecasted demand for next period
257    pub forecasted_demand: Decimal,
258    /// Confidence level (0-1)
259    pub confidence: Decimal,
260    /// Current stock
261    pub current_stock: Decimal,
262    /// Days until stockout (if no reorder)
263    pub days_until_stockout: Option<i32>,
264    /// Recommended reorder quantity
265    pub recommended_reorder_qty: Option<Decimal>,
266    /// Recommended reorder date
267    pub recommended_reorder_date: Option<DateTime<Utc>>,
268    /// Trend direction
269    pub trend: Trend,
270}
271
272/// Trend direction
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
274#[serde(rename_all = "snake_case")]
275#[non_exhaustive]
276pub enum Trend {
277    Rising,
278    Stable,
279    Falling,
280}
281
282impl Default for Trend {
283    fn default() -> Self {
284        Self::Stable
285    }
286}
287
288/// Revenue forecast
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct RevenueForecast {
291    /// Period being forecasted
292    pub period: String,
293    /// Forecasted revenue
294    pub forecasted_revenue: Decimal,
295    /// Lower bound (confidence interval)
296    pub lower_bound: Decimal,
297    /// Upper bound (confidence interval)
298    pub upper_bound: Decimal,
299    /// Confidence level (e.g., 0.95 for 95%)
300    pub confidence_level: Decimal,
301    /// Based on historical data from
302    pub based_on_periods: u32,
303}
304
305// ============================================================================
306// Order Analytics
307// ============================================================================
308
309/// Order status breakdown
310#[derive(Debug, Clone, Default, Serialize, Deserialize)]
311pub struct OrderStatusBreakdown {
312    pub pending: u64,
313    pub confirmed: u64,
314    pub processing: u64,
315    pub shipped: u64,
316    pub delivered: u64,
317    pub cancelled: u64,
318    pub refunded: u64,
319    pub total: u64,
320}
321
322/// Order fulfillment metrics
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct FulfillmentMetrics {
325    /// Average time from order to shipped (in hours)
326    pub avg_time_to_ship_hours: Option<Decimal>,
327    /// Average time from shipped to delivered (in hours)
328    pub avg_time_to_deliver_hours: Option<Decimal>,
329    /// Percentage of orders shipped on time
330    pub on_time_shipping_percent: Option<Decimal>,
331    /// Percentage of orders delivered on time
332    pub on_time_delivery_percent: Option<Decimal>,
333    /// Orders shipped today
334    pub shipped_today: u64,
335    /// Orders awaiting shipment
336    pub awaiting_shipment: u64,
337}
338
339// ============================================================================
340// Return Analytics
341// ============================================================================
342
343/// Return metrics
344#[derive(Debug, Clone, Default, Serialize, Deserialize)]
345pub struct ReturnMetrics {
346    /// Total returns in period
347    pub total_returns: u64,
348    /// Return rate (returns / orders)
349    pub return_rate_percent: Decimal,
350    /// Total refund amount
351    pub total_refunded: Decimal,
352    /// Returns by reason breakdown
353    pub by_reason: Vec<ReturnReasonCount>,
354    /// Most returned products
355    pub top_returned_products: Vec<TopReturnedProduct>,
356}
357
358/// Return count by reason
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct ReturnReasonCount {
361    pub reason: String,
362    pub count: u64,
363    pub percentage: Decimal,
364}
365
366/// Product with high return rate
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct TopReturnedProduct {
369    pub sku: String,
370    pub name: String,
371    pub units_returned: u64,
372    pub units_sold: u64,
373    pub return_rate_percent: Decimal,
374}
375
376// ============================================================================
377// Query Parameters
378// ============================================================================
379
380/// Common analytics query parameters
381#[derive(Debug, Clone, Default, Serialize, Deserialize)]
382pub struct AnalyticsQuery {
383    /// Time period preset
384    pub period: Option<TimePeriod>,
385    /// Custom date range (used when period is Custom)
386    pub date_range: Option<DateRange>,
387    /// Time granularity for time-series data
388    pub granularity: Option<TimeGranularity>,
389    /// Number of results to return (for top-N queries)
390    pub limit: Option<u32>,
391    /// Compare to previous period
392    pub compare_previous: Option<bool>,
393}
394
395impl AnalyticsQuery {
396    /// Create a new analytics query with defaults
397    #[must_use]
398    pub fn new() -> Self {
399        Self::default()
400    }
401
402    /// Set a predefined time period
403    #[must_use]
404    pub const fn period(mut self, period: TimePeriod) -> Self {
405        self.period = Some(period);
406        self
407    }
408
409    /// Set a custom date range and switch to custom period
410    #[must_use]
411    pub const fn date_range(mut self, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
412        self.period = Some(TimePeriod::Custom);
413        self.date_range = Some(DateRange { start: Some(start), end: Some(end) });
414        self
415    }
416
417    /// Set the time granularity for time-series results
418    #[must_use]
419    pub const fn granularity(mut self, granularity: TimeGranularity) -> Self {
420        self.granularity = Some(granularity);
421        self
422    }
423
424    /// Limit the number of results returned
425    #[must_use]
426    pub const fn limit(mut self, limit: u32) -> Self {
427        self.limit = Some(limit);
428        self
429    }
430
431    /// Enable or disable comparison to the previous period
432    #[must_use]
433    pub const fn compare_previous(mut self, compare: bool) -> Self {
434        self.compare_previous = Some(compare);
435        self
436    }
437}