fallow_cli/health_types/trends.rs
1//! Trend types — comparing current run against a saved snapshot.
2
3/// Health trend comparison: current run vs. a previous snapshot.
4#[derive(Debug, Clone, serde::Serialize)]
5pub struct HealthTrend {
6 /// The snapshot being compared against.
7 pub compared_to: TrendPoint,
8 /// Per-metric deltas.
9 pub metrics: Vec<TrendMetric>,
10 /// Number of snapshots found in the snapshot directory.
11 pub snapshots_loaded: usize,
12 /// Overall direction across all metrics.
13 pub overall_direction: TrendDirection,
14}
15
16/// A reference to a snapshot used in trend comparison.
17#[derive(Debug, Clone, serde::Serialize)]
18pub struct TrendPoint {
19 /// ISO 8601 timestamp of the snapshot.
20 pub timestamp: String,
21 /// Git SHA at time of snapshot.
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub git_sha: Option<String>,
24 /// Health score from the snapshot (stored, not re-derived).
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub score: Option<f64>,
27 /// Letter grade from the snapshot.
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub grade: Option<String>,
30}
31
32/// A single metric's trend between two snapshots.
33#[derive(Debug, Clone, serde::Serialize)]
34pub struct TrendMetric {
35 /// Metric identifier (e.g., `"score"`, `"dead_file_pct"`).
36 pub name: &'static str,
37 /// Human-readable label (e.g., `"Health Score"`, `"Dead Files"`).
38 pub label: &'static str,
39 /// Previous value (from snapshot).
40 pub previous: f64,
41 /// Current value (from this run).
42 pub current: f64,
43 /// Absolute change (current − previous).
44 pub delta: f64,
45 /// Direction of change.
46 pub direction: TrendDirection,
47 /// Unit for display (e.g., `"%"`, `""`, `"pts"`).
48 pub unit: &'static str,
49 /// Raw count from previous snapshot (for JSON consumers).
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub previous_count: Option<TrendCount>,
52 /// Raw count from current run (for JSON consumers).
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub current_count: Option<TrendCount>,
55}
56
57/// Raw numerator/denominator for a percentage metric.
58#[derive(Debug, Clone, serde::Serialize)]
59pub struct TrendCount {
60 /// The numerator (e.g., dead files count).
61 pub value: usize,
62 /// The denominator (e.g., total files).
63 pub total: usize,
64}
65
66/// Direction of a metric's change, semantically (improving/declining/stable).
67#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
68#[serde(rename_all = "snake_case")]
69pub enum TrendDirection {
70 /// The metric moved in a beneficial direction.
71 Improving,
72 /// The metric moved in a detrimental direction.
73 Declining,
74 /// The metric stayed within tolerance.
75 Stable,
76}
77
78impl TrendDirection {
79 /// Arrow symbol for terminal output.
80 #[must_use]
81 pub const fn arrow(self) -> &'static str {
82 match self {
83 Self::Improving => "\u{2191}", // ↑
84 Self::Declining => "\u{2193}", // ↓
85 Self::Stable => "\u{2192}", // →
86 }
87 }
88
89 /// Human-readable label.
90 #[must_use]
91 pub const fn label(self) -> &'static str {
92 match self {
93 Self::Improving => "improving",
94 Self::Declining => "declining",
95 Self::Stable => "stable",
96 }
97 }
98}