Skip to main content

tga_core/models/
mod.rs

1//! Domain models corresponding to the v1 database schema.
2//!
3//! These structs are the in-memory representation of rows in the core
4//! tables. They are intentionally serialization-friendly via `serde` so
5//! that they can be emitted as JSON in reports without an intermediate
6//! DTO layer.
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11/// A single commit observed in a repository.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Commit {
14    /// Primary key (database-assigned).
15    pub id: i64,
16
17    /// Full git OID (hex).
18    pub sha: String,
19
20    /// Foreign key into [`Author`].
21    pub author_id: Option<i64>,
22
23    /// Author display name as recorded in the commit.
24    pub author_name: String,
25
26    /// Author email as recorded in the commit.
27    pub author_email: String,
28
29    /// Author timestamp (UTC).
30    pub timestamp: DateTime<Utc>,
31
32    /// Commit message body (raw).
33    pub message: String,
34
35    /// Repository identifier (path or canonical name).
36    pub repository: String,
37
38    /// Number of files changed.
39    pub files_changed: u32,
40
41    /// Lines added.
42    pub insertions: u32,
43
44    /// Lines deleted.
45    pub deletions: u32,
46
47    /// Foreign key into [`Classification`], if classified.
48    pub classification_id: Option<i64>,
49
50    /// Confidence assigned by the classifier (0.0–1.0).
51    pub confidence: Option<f64>,
52
53    /// True for merge commits (parents > 1).
54    pub is_merge: bool,
55}
56
57/// A canonical author / developer identity.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Author {
60    /// Primary key (database-assigned).
61    pub id: i64,
62
63    /// Canonical display name.
64    pub canonical_name: String,
65
66    /// Canonical email address.
67    pub canonical_email: String,
68
69    /// JSON-encoded array of alias strings (names or emails).
70    pub aliases: String,
71}
72
73/// A classification verdict produced by the cascade.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Classification {
76    /// Primary key (database-assigned).
77    pub id: i64,
78
79    /// Top-level category (e.g. `feature`, `bugfix`, `chore`).
80    pub category: String,
81
82    /// Optional finer-grained label.
83    pub subcategory: Option<String>,
84
85    /// Associated ticket identifier (e.g. `API-123`), if any.
86    pub ticket_id: Option<String>,
87
88    /// Confidence in this verdict (0.0–1.0).
89    pub confidence: f64,
90
91    /// Which tier of the cascade produced this verdict.
92    pub method: ClassificationMethod,
93}
94
95/// File-level change record attached to a commit.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct FileChange {
98    /// Primary key (database-assigned).
99    pub id: i64,
100
101    /// Foreign key into [`Commit`].
102    pub commit_id: i64,
103
104    /// Relative path within the repository.
105    pub path: String,
106
107    /// Type of change.
108    pub change_type: ChangeType,
109
110    /// Lines added in this file.
111    pub insertions: u32,
112
113    /// Lines deleted in this file.
114    pub deletions: u32,
115}
116
117/// A pull request record (typically GitHub).
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct PullRequest {
120    /// Primary key (database-assigned).
121    pub id: i64,
122
123    /// PR number within its repository.
124    pub pr_number: u64,
125
126    /// PR title.
127    pub title: String,
128
129    /// Author login.
130    pub author: String,
131
132    /// Lifecycle state.
133    pub state: PrState,
134
135    /// PR creation timestamp (UTC).
136    pub created_at: DateTime<Utc>,
137
138    /// Merge timestamp, if merged.
139    pub merged_at: Option<DateTime<Utc>>,
140
141    /// JSON-encoded array of commit SHAs in the PR.
142    pub commit_shas: String,
143}
144
145/// Cascade tier that produced a classification.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
147#[serde(rename_all = "snake_case")]
148pub enum ClassificationMethod {
149    /// Matched a deterministic exact rule.
150    ExactRule,
151    /// Matched a regex rule.
152    RegexRule,
153    /// Matched via fuzzy similarity.
154    FuzzyMatch,
155    /// Assigned by an LLM fallback.
156    LlmFallback,
157    /// Set manually by a user override.
158    Manual,
159}
160
161impl ClassificationMethod {
162    /// Stable string representation used for DB storage.
163    pub fn as_str(&self) -> &'static str {
164        match self {
165            ClassificationMethod::ExactRule => "exact_rule",
166            ClassificationMethod::RegexRule => "regex_rule",
167            ClassificationMethod::FuzzyMatch => "fuzzy_match",
168            ClassificationMethod::LlmFallback => "llm_fallback",
169            ClassificationMethod::Manual => "manual",
170        }
171    }
172}
173
174/// File change kind for [`FileChange`].
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(rename_all = "snake_case")]
177pub enum ChangeType {
178    /// File was added.
179    Added,
180    /// File contents were modified.
181    Modified,
182    /// File was deleted.
183    Deleted,
184    /// File was renamed (and possibly modified).
185    Renamed,
186}
187
188impl ChangeType {
189    /// Stable string representation used for DB storage.
190    pub fn as_str(&self) -> &'static str {
191        match self {
192            ChangeType::Added => "added",
193            ChangeType::Modified => "modified",
194            ChangeType::Deleted => "deleted",
195            ChangeType::Renamed => "renamed",
196        }
197    }
198}
199
200/// Lifecycle state of a [`PullRequest`].
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(rename_all = "snake_case")]
203pub enum PrState {
204    /// PR is open.
205    Open,
206    /// PR was closed without merging.
207    Closed,
208    /// PR was merged.
209    Merged,
210}
211
212impl PrState {
213    /// Stable string representation used for DB storage.
214    pub fn as_str(&self) -> &'static str {
215        match self {
216            PrState::Open => "open",
217            PrState::Closed => "closed",
218            PrState::Merged => "merged",
219        }
220    }
221}