vct_core/analysis/
summary.rs1use crate::analysis::{AggregatedAnalysisRow, PerProviderAnalysisRows};
8use crate::models::ProviderActiveDays;
9
10#[derive(Default)]
16pub struct AnalysisRow {
17 pub model: String,
19 pub edit_lines: usize,
21 pub read_lines: usize,
23 pub write_lines: usize,
25 pub bash_count: usize,
27 pub edit_count: usize,
29 pub read_count: usize,
31 pub todo_write_count: usize,
33 pub write_count: usize,
35}
36
37#[derive(Default, Clone)]
41pub struct AnalysisProviderStats {
42 pub total_edit_lines: usize,
44 pub total_read_lines: usize,
46 pub total_write_lines: usize,
48 pub total_bash_count: usize,
50 pub total_edit_count: usize,
52 pub total_read_count: usize,
54 pub total_todo_write_count: usize,
56 pub total_write_count: usize,
58 pub days_count: usize,
60}
61
62impl AnalysisProviderStats {
63 fn accumulate_row(&mut self, row: &AnalysisRow) {
65 self.total_edit_lines += row.edit_lines;
66 self.total_read_lines += row.read_lines;
67 self.total_write_lines += row.write_lines;
68 self.total_bash_count += row.bash_count;
69 self.total_edit_count += row.edit_count;
70 self.total_read_count += row.read_count;
71 self.total_todo_write_count += row.todo_write_count;
72 self.total_write_count += row.write_count;
73 }
74}
75
76pub type AnalysisProviderTotals = crate::models::ProviderTotals<AnalysisProviderStats>;
78
79pub fn calculate_analysis_provider_totals_from_per_provider(
88 per_provider: &PerProviderAnalysisRows,
89 provider_days: &ProviderActiveDays,
90) -> AnalysisProviderTotals {
91 let mut totals = AnalysisProviderTotals::default();
92
93 totals.claude.days_count = provider_days.claude;
94 totals.codex.days_count = provider_days.codex;
95 totals.copilot.days_count = provider_days.copilot;
96 totals.gemini.days_count = provider_days.gemini;
97 totals.grok.days_count = provider_days.grok;
98 totals.opencode.days_count = provider_days.opencode;
99 totals.cursor.days_count = provider_days.cursor;
100 totals.overall.days_count = provider_days.total;
101
102 accumulate_analysis_provider(&mut totals.claude, &per_provider.claude);
103 accumulate_analysis_provider(&mut totals.codex, &per_provider.codex);
104 accumulate_analysis_provider(&mut totals.copilot, &per_provider.copilot);
105 accumulate_analysis_provider(&mut totals.gemini, &per_provider.gemini);
106 accumulate_analysis_provider(&mut totals.grok, &per_provider.grok);
107 accumulate_analysis_provider(&mut totals.opencode, &per_provider.opencode);
108 accumulate_analysis_provider(&mut totals.cursor, &per_provider.cursor);
109
110 totals.overall.total_edit_lines = totals.claude.total_edit_lines
115 + totals.codex.total_edit_lines
116 + totals.copilot.total_edit_lines
117 + totals.gemini.total_edit_lines
118 + totals.grok.total_edit_lines
119 + totals.opencode.total_edit_lines
120 + totals.cursor.total_edit_lines;
121 totals.overall.total_read_lines = totals.claude.total_read_lines
122 + totals.codex.total_read_lines
123 + totals.copilot.total_read_lines
124 + totals.gemini.total_read_lines
125 + totals.grok.total_read_lines
126 + totals.opencode.total_read_lines
127 + totals.cursor.total_read_lines;
128 totals.overall.total_write_lines = totals.claude.total_write_lines
129 + totals.codex.total_write_lines
130 + totals.copilot.total_write_lines
131 + totals.gemini.total_write_lines
132 + totals.grok.total_write_lines
133 + totals.opencode.total_write_lines
134 + totals.cursor.total_write_lines;
135 totals.overall.total_bash_count = totals.claude.total_bash_count
136 + totals.codex.total_bash_count
137 + totals.copilot.total_bash_count
138 + totals.gemini.total_bash_count
139 + totals.grok.total_bash_count
140 + totals.opencode.total_bash_count
141 + totals.cursor.total_bash_count;
142 totals.overall.total_edit_count = totals.claude.total_edit_count
143 + totals.codex.total_edit_count
144 + totals.copilot.total_edit_count
145 + totals.gemini.total_edit_count
146 + totals.grok.total_edit_count
147 + totals.opencode.total_edit_count
148 + totals.cursor.total_edit_count;
149 totals.overall.total_read_count = totals.claude.total_read_count
150 + totals.codex.total_read_count
151 + totals.copilot.total_read_count
152 + totals.gemini.total_read_count
153 + totals.grok.total_read_count
154 + totals.opencode.total_read_count
155 + totals.cursor.total_read_count;
156 totals.overall.total_todo_write_count = totals.claude.total_todo_write_count
157 + totals.codex.total_todo_write_count
158 + totals.copilot.total_todo_write_count
159 + totals.gemini.total_todo_write_count
160 + totals.grok.total_todo_write_count
161 + totals.opencode.total_todo_write_count
162 + totals.cursor.total_todo_write_count;
163 totals.overall.total_write_count = totals.claude.total_write_count
164 + totals.codex.total_write_count
165 + totals.copilot.total_write_count
166 + totals.gemini.total_write_count
167 + totals.grok.total_write_count
168 + totals.opencode.total_write_count
169 + totals.cursor.total_write_count;
170
171 totals
172}
173
174fn accumulate_analysis_provider(stats: &mut AnalysisProviderStats, rows: &[AggregatedAnalysisRow]) {
176 let analysis_rows = convert_to_analysis_rows(rows);
177 for row in &analysis_rows {
178 stats.accumulate_row(row);
179 }
180}
181
182pub fn convert_to_analysis_rows(data: &[AggregatedAnalysisRow]) -> Vec<AnalysisRow> {
212 data.iter()
213 .map(|row| AnalysisRow {
214 model: row.model.clone(),
215 edit_lines: row.edit_lines,
216 read_lines: row.read_lines,
217 write_lines: row.write_lines,
218 bash_count: row.bash_count,
219 edit_count: row.edit_count,
220 read_count: row.read_count,
221 todo_write_count: row.todo_write_count,
222 write_count: row.write_count,
223 })
224 .collect()
225}