sdivi_snapshot/change_coupling.rs
1//! Change-coupling result types stored in [`super::snapshot::Snapshot`].
2
3use serde::{Deserialize, Serialize};
4
5/// A single file-pair that co-changes above the configured minimum frequency.
6///
7/// # Examples
8///
9/// ```rust
10/// use sdivi_snapshot::change_coupling::CoChangePair;
11///
12/// let pair = CoChangePair {
13/// source: "src/a.rs".to_string(),
14/// target: "src/b.rs".to_string(),
15/// frequency: 0.8,
16/// cochange_count: 4,
17/// };
18/// assert!(pair.source < pair.target);
19/// ```
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub struct CoChangePair {
22 /// The lexicographically smaller file path (always `source < target`).
23 pub source: String,
24 /// The lexicographically larger file path.
25 pub target: String,
26 /// Co-change frequency: `cochange_count / commits_analyzed`.
27 pub frequency: f64,
28 /// Number of commits in the analysis window that touched both files.
29 pub cochange_count: u32,
30}
31
32/// Result of the change-coupling analysis for one snapshot.
33///
34/// `None` in the snapshot when the repo has no git history or
35/// `history_depth = 0` was configured.
36///
37/// # Examples
38///
39/// ```rust
40/// use sdivi_snapshot::change_coupling::ChangeCouplingResult;
41///
42/// let result = ChangeCouplingResult {
43/// pairs: vec![],
44/// commits_analyzed: 0,
45/// distinct_files_touched: 0,
46/// };
47/// assert_eq!(result.commits_analyzed, 0);
48/// ```
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
50pub struct ChangeCouplingResult {
51 /// File pairs whose co-change frequency meets `min_frequency` and
52 /// whose `cochange_count >= 2`. Sorted by `(source, target)`.
53 pub pairs: Vec<CoChangePair>,
54 /// Number of commits actually analyzed (≤ `history_depth`).
55 pub commits_analyzed: u32,
56 /// Count of unique file paths that appear in at least one analyzed commit.
57 pub distinct_files_touched: u32,
58}