gitflow-analytics 1.0.3__py3-none-any.whl → 1.3.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitflow_analytics/_version.py +1 -1
- gitflow_analytics/classification/__init__.py +31 -0
- gitflow_analytics/classification/batch_classifier.py +752 -0
- gitflow_analytics/classification/classifier.py +464 -0
- gitflow_analytics/classification/feature_extractor.py +725 -0
- gitflow_analytics/classification/linguist_analyzer.py +574 -0
- gitflow_analytics/classification/model.py +455 -0
- gitflow_analytics/cli.py +4158 -350
- gitflow_analytics/cli_rich.py +198 -48
- gitflow_analytics/config/__init__.py +43 -0
- gitflow_analytics/config/errors.py +261 -0
- gitflow_analytics/config/loader.py +905 -0
- gitflow_analytics/config/profiles.py +264 -0
- gitflow_analytics/config/repository.py +124 -0
- gitflow_analytics/config/schema.py +444 -0
- gitflow_analytics/config/validator.py +154 -0
- gitflow_analytics/config.py +44 -508
- gitflow_analytics/core/analyzer.py +1209 -98
- gitflow_analytics/core/cache.py +1337 -29
- gitflow_analytics/core/data_fetcher.py +1285 -0
- gitflow_analytics/core/identity.py +363 -14
- gitflow_analytics/core/metrics_storage.py +526 -0
- gitflow_analytics/core/progress.py +372 -0
- gitflow_analytics/core/schema_version.py +269 -0
- gitflow_analytics/extractors/ml_tickets.py +1100 -0
- gitflow_analytics/extractors/story_points.py +8 -1
- gitflow_analytics/extractors/tickets.py +749 -11
- gitflow_analytics/identity_llm/__init__.py +6 -0
- gitflow_analytics/identity_llm/analysis_pass.py +231 -0
- gitflow_analytics/identity_llm/analyzer.py +464 -0
- gitflow_analytics/identity_llm/models.py +76 -0
- gitflow_analytics/integrations/github_integration.py +175 -11
- gitflow_analytics/integrations/jira_integration.py +461 -24
- gitflow_analytics/integrations/orchestrator.py +124 -1
- gitflow_analytics/metrics/activity_scoring.py +322 -0
- gitflow_analytics/metrics/branch_health.py +470 -0
- gitflow_analytics/metrics/dora.py +379 -20
- gitflow_analytics/models/database.py +843 -53
- gitflow_analytics/pm_framework/__init__.py +115 -0
- gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
- gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
- gitflow_analytics/pm_framework/base.py +406 -0
- gitflow_analytics/pm_framework/models.py +211 -0
- gitflow_analytics/pm_framework/orchestrator.py +652 -0
- gitflow_analytics/pm_framework/registry.py +333 -0
- gitflow_analytics/qualitative/__init__.py +9 -10
- gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
- gitflow_analytics/qualitative/classifiers/__init__.py +3 -3
- gitflow_analytics/qualitative/classifiers/change_type.py +518 -244
- gitflow_analytics/qualitative/classifiers/domain_classifier.py +272 -165
- gitflow_analytics/qualitative/classifiers/intent_analyzer.py +321 -222
- gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
- gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
- gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
- gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
- gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
- gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
- gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
- gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
- gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
- gitflow_analytics/qualitative/classifiers/risk_analyzer.py +215 -189
- gitflow_analytics/qualitative/core/__init__.py +4 -4
- gitflow_analytics/qualitative/core/llm_fallback.py +239 -235
- gitflow_analytics/qualitative/core/nlp_engine.py +157 -148
- gitflow_analytics/qualitative/core/pattern_cache.py +214 -192
- gitflow_analytics/qualitative/core/processor.py +381 -248
- gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
- gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
- gitflow_analytics/qualitative/models/__init__.py +7 -7
- gitflow_analytics/qualitative/models/schemas.py +155 -121
- gitflow_analytics/qualitative/utils/__init__.py +4 -4
- gitflow_analytics/qualitative/utils/batch_processor.py +136 -123
- gitflow_analytics/qualitative/utils/cost_tracker.py +142 -140
- gitflow_analytics/qualitative/utils/metrics.py +172 -158
- gitflow_analytics/qualitative/utils/text_processing.py +146 -104
- gitflow_analytics/reports/__init__.py +100 -0
- gitflow_analytics/reports/analytics_writer.py +539 -14
- gitflow_analytics/reports/base.py +648 -0
- gitflow_analytics/reports/branch_health_writer.py +322 -0
- gitflow_analytics/reports/classification_writer.py +924 -0
- gitflow_analytics/reports/cli_integration.py +427 -0
- gitflow_analytics/reports/csv_writer.py +1676 -212
- gitflow_analytics/reports/data_models.py +504 -0
- gitflow_analytics/reports/database_report_generator.py +427 -0
- gitflow_analytics/reports/example_usage.py +344 -0
- gitflow_analytics/reports/factory.py +499 -0
- gitflow_analytics/reports/formatters.py +698 -0
- gitflow_analytics/reports/html_generator.py +1116 -0
- gitflow_analytics/reports/interfaces.py +489 -0
- gitflow_analytics/reports/json_exporter.py +2770 -0
- gitflow_analytics/reports/narrative_writer.py +2287 -158
- gitflow_analytics/reports/story_point_correlation.py +1144 -0
- gitflow_analytics/reports/weekly_trends_writer.py +389 -0
- gitflow_analytics/training/__init__.py +5 -0
- gitflow_analytics/training/model_loader.py +377 -0
- gitflow_analytics/training/pipeline.py +550 -0
- gitflow_analytics/tui/__init__.py +1 -1
- gitflow_analytics/tui/app.py +129 -126
- gitflow_analytics/tui/screens/__init__.py +3 -3
- gitflow_analytics/tui/screens/analysis_progress_screen.py +188 -179
- gitflow_analytics/tui/screens/configuration_screen.py +154 -178
- gitflow_analytics/tui/screens/loading_screen.py +100 -110
- gitflow_analytics/tui/screens/main_screen.py +89 -72
- gitflow_analytics/tui/screens/results_screen.py +305 -281
- gitflow_analytics/tui/widgets/__init__.py +2 -2
- gitflow_analytics/tui/widgets/data_table.py +67 -69
- gitflow_analytics/tui/widgets/export_modal.py +76 -76
- gitflow_analytics/tui/widgets/progress_widget.py +41 -46
- gitflow_analytics-1.3.11.dist-info/METADATA +1015 -0
- gitflow_analytics-1.3.11.dist-info/RECORD +122 -0
- gitflow_analytics-1.0.3.dist-info/METADATA +0 -490
- gitflow_analytics-1.0.3.dist-info/RECORD +0 -62
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/WHEEL +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/licenses/LICENSE +0 -0
- {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/top_level.txt +0 -0
gitflow_analytics/cli_rich.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Rich CLI components for GitFlow Analytics with beautiful terminal output."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
4
3
|
from pathlib import Path
|
|
5
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Optional
|
|
6
5
|
|
|
7
6
|
from rich import box
|
|
8
7
|
from rich.console import Console
|
|
@@ -18,7 +17,6 @@ from rich.progress import (
|
|
|
18
17
|
TimeElapsedColumn,
|
|
19
18
|
)
|
|
20
19
|
from rich.table import Table
|
|
21
|
-
from rich.text import Text
|
|
22
20
|
from rich.tree import Tree
|
|
23
21
|
|
|
24
22
|
from ._version import __version__
|
|
@@ -27,11 +25,11 @@ from ._version import __version__
|
|
|
27
25
|
class RichProgressDisplay:
|
|
28
26
|
"""
|
|
29
27
|
Rich terminal display for GitFlow Analytics progress and results.
|
|
30
|
-
|
|
28
|
+
|
|
31
29
|
WHY: Provides a clean, structured interface that shows users exactly what's happening
|
|
32
30
|
during analysis without the complexity of a full TUI. Uses Rich library for
|
|
33
31
|
beautiful terminal output with progress bars, tables, and status indicators.
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
DESIGN DECISION: Chose to use Rich's Live display for real-time updates because:
|
|
36
34
|
- Allows multiple progress bars and status updates in a single view
|
|
37
35
|
- Provides structured layout with panels and tables
|
|
@@ -51,7 +49,7 @@ class RichProgressDisplay:
|
|
|
51
49
|
console=self.console,
|
|
52
50
|
)
|
|
53
51
|
self.live: Optional[Live] = None
|
|
54
|
-
self._tasks:
|
|
52
|
+
self._tasks: dict[str, TaskID] = {}
|
|
55
53
|
|
|
56
54
|
def show_header(self) -> None:
|
|
57
55
|
"""Display the application header."""
|
|
@@ -74,30 +72,30 @@ class RichProgressDisplay:
|
|
|
74
72
|
) -> None:
|
|
75
73
|
"""
|
|
76
74
|
Display configuration validation status.
|
|
77
|
-
|
|
75
|
+
|
|
78
76
|
WHY: Users need immediate feedback on whether their configuration is valid
|
|
79
77
|
before starting analysis. This prevents wasted time on invalid configs.
|
|
80
78
|
"""
|
|
81
79
|
config_tree = Tree("[bold]Configuration Status[/bold]")
|
|
82
|
-
|
|
80
|
+
|
|
83
81
|
# Config file status
|
|
84
82
|
config_tree.add(f"[green]✓[/green] Config: {config_path}")
|
|
85
|
-
|
|
83
|
+
|
|
86
84
|
# GitHub configuration
|
|
87
85
|
if github_org:
|
|
88
86
|
github_status = "[green]✓[/green]" if github_token_valid else "[red]✗[/red]"
|
|
89
87
|
token_status = "Token: ✓" if github_token_valid else "Token: ✗"
|
|
90
88
|
config_tree.add(f"{github_status} GitHub: {github_org} ({token_status})")
|
|
91
|
-
|
|
89
|
+
|
|
92
90
|
# JIRA configuration
|
|
93
91
|
if jira_configured:
|
|
94
92
|
jira_status = "[green]✓[/green]" if jira_valid else "[red]✗[/red]"
|
|
95
93
|
cred_status = "Credentials: ✓" if jira_valid else "Credentials: ✗"
|
|
96
94
|
config_tree.add(f"{jira_status} JIRA: configured ({cred_status})")
|
|
97
|
-
|
|
95
|
+
|
|
98
96
|
# Analysis period
|
|
99
97
|
config_tree.add(f"Analysis Period: {analysis_weeks} weeks")
|
|
100
|
-
|
|
98
|
+
|
|
101
99
|
self.console.print(config_tree)
|
|
102
100
|
self.console.print()
|
|
103
101
|
|
|
@@ -117,7 +115,9 @@ class RichProgressDisplay:
|
|
|
117
115
|
task_id = self.progress.add_task(description, total=total)
|
|
118
116
|
self._tasks[name] = task_id
|
|
119
117
|
|
|
120
|
-
def update_progress_task(
|
|
118
|
+
def update_progress_task(
|
|
119
|
+
self, name: str, advance: int = 1, description: Optional[str] = None
|
|
120
|
+
) -> None:
|
|
121
121
|
"""Update progress for a specific task."""
|
|
122
122
|
if name in self._tasks:
|
|
123
123
|
kwargs = {"advance": advance}
|
|
@@ -135,10 +135,10 @@ class RichProgressDisplay:
|
|
|
135
135
|
if description:
|
|
136
136
|
self.progress.update(self._tasks[name], description=description)
|
|
137
137
|
|
|
138
|
-
def show_repository_discovery(self, repos:
|
|
138
|
+
def show_repository_discovery(self, repos: list[dict[str, Any]]) -> None:
|
|
139
139
|
"""
|
|
140
140
|
Display repository discovery results.
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
WHY: Users need to see which repositories were discovered and their status
|
|
143
143
|
before analysis begins, especially with organization-based discovery.
|
|
144
144
|
"""
|
|
@@ -146,7 +146,7 @@ class RichProgressDisplay:
|
|
|
146
146
|
return
|
|
147
147
|
|
|
148
148
|
self.console.print(f"[bold]Repository Discovery[/bold] - Found {len(repos)} repositories")
|
|
149
|
-
|
|
149
|
+
|
|
150
150
|
repo_tree = Tree("")
|
|
151
151
|
for repo in repos:
|
|
152
152
|
status = "[green]✓[/green]" if repo.get("exists", True) else "[red]✗[/red]"
|
|
@@ -156,7 +156,7 @@ class RichProgressDisplay:
|
|
|
156
156
|
repo_tree.add(f"{status} {name} ({github_repo})")
|
|
157
157
|
else:
|
|
158
158
|
repo_tree.add(f"{status} {name}")
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
self.console.print(repo_tree)
|
|
161
161
|
self.console.print()
|
|
162
162
|
|
|
@@ -171,31 +171,31 @@ class RichProgressDisplay:
|
|
|
171
171
|
) -> None:
|
|
172
172
|
"""
|
|
173
173
|
Display analysis results summary.
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
WHY: Provides users with key metrics at a glance after analysis completes.
|
|
176
176
|
Uses a structured table format for easy scanning of important numbers.
|
|
177
177
|
"""
|
|
178
178
|
self.console.print()
|
|
179
|
-
|
|
179
|
+
|
|
180
180
|
summary_table = Table(title="[bold]Analysis Summary[/bold]", box=box.ROUNDED)
|
|
181
181
|
summary_table.add_column("Metric", style="cyan", width=20)
|
|
182
182
|
summary_table.add_column("Value", style="green", width=15)
|
|
183
|
-
|
|
183
|
+
|
|
184
184
|
summary_table.add_row("Total Commits", f"{total_commits:,}")
|
|
185
185
|
summary_table.add_row("Total PRs", f"{total_prs:,}")
|
|
186
186
|
summary_table.add_row("Active Developers", f"{active_developers:,}")
|
|
187
187
|
summary_table.add_row("Ticket Coverage", f"{ticket_coverage:.1f}%")
|
|
188
188
|
summary_table.add_row("Story Points", f"{story_points:,}")
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
if qualitative_analyzed > 0:
|
|
191
191
|
summary_table.add_row("Qualitative Analysis", f"{qualitative_analyzed:,} commits")
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
self.console.print(summary_table)
|
|
194
194
|
|
|
195
|
-
def show_dora_metrics(self, dora_metrics:
|
|
195
|
+
def show_dora_metrics(self, dora_metrics: dict[str, Any]) -> None:
|
|
196
196
|
"""
|
|
197
197
|
Display DORA metrics in a structured format.
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
WHY: DORA metrics are key performance indicators that teams care about.
|
|
200
200
|
Displaying them prominently helps users understand their team's performance level.
|
|
201
201
|
"""
|
|
@@ -203,37 +203,37 @@ class RichProgressDisplay:
|
|
|
203
203
|
return
|
|
204
204
|
|
|
205
205
|
self.console.print()
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
dora_table = Table(title="[bold]DORA Metrics[/bold]", box=box.ROUNDED)
|
|
208
208
|
dora_table.add_column("Metric", style="cyan", width=25)
|
|
209
209
|
dora_table.add_column("Value", style="yellow", width=20)
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
# Deployment frequency
|
|
212
212
|
df_category = dora_metrics.get("deployment_frequency", {}).get("category", "Unknown")
|
|
213
213
|
dora_table.add_row("Deployment Frequency", df_category)
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
# Lead time
|
|
216
216
|
lead_time = dora_metrics.get("lead_time_hours", 0)
|
|
217
217
|
dora_table.add_row("Lead Time", f"{lead_time:.1f} hours")
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
# Change failure rate
|
|
220
220
|
cfr = dora_metrics.get("change_failure_rate", 0)
|
|
221
221
|
dora_table.add_row("Change Failure Rate", f"{cfr:.1f}%")
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
# MTTR
|
|
224
224
|
mttr = dora_metrics.get("mttr_hours", 0)
|
|
225
225
|
dora_table.add_row("MTTR", f"{mttr:.1f} hours")
|
|
226
|
-
|
|
226
|
+
|
|
227
227
|
# Performance level
|
|
228
228
|
perf_level = dora_metrics.get("performance_level", "Unknown")
|
|
229
229
|
dora_table.add_row("Performance Level", f"[bold]{perf_level}[/bold]")
|
|
230
|
-
|
|
230
|
+
|
|
231
231
|
self.console.print(dora_table)
|
|
232
232
|
|
|
233
|
-
def show_qualitative_stats(self, qual_stats:
|
|
233
|
+
def show_qualitative_stats(self, qual_stats: dict[str, Any]) -> None:
|
|
234
234
|
"""
|
|
235
235
|
Display qualitative analysis statistics.
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
WHY: Qualitative analysis can be expensive (time/cost), so users need
|
|
238
238
|
visibility into processing efficiency and costs incurred.
|
|
239
239
|
"""
|
|
@@ -242,17 +242,17 @@ class RichProgressDisplay:
|
|
|
242
242
|
|
|
243
243
|
processing_summary = qual_stats.get("processing_summary", {})
|
|
244
244
|
llm_stats = qual_stats.get("llm_statistics", {})
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
self.console.print()
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
qual_table = Table(title="[bold]Qualitative Analysis Stats[/bold]", box=box.ROUNDED)
|
|
249
249
|
qual_table.add_column("Metric", style="cyan", width=25)
|
|
250
250
|
qual_table.add_column("Value", style="magenta", width=20)
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
# Processing speed
|
|
253
253
|
commits_per_sec = processing_summary.get("commits_per_second", 0)
|
|
254
254
|
qual_table.add_row("Processing Speed", f"{commits_per_sec:.1f} commits/sec")
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
# Method breakdown
|
|
257
257
|
method_breakdown = processing_summary.get("method_breakdown", {})
|
|
258
258
|
cache_pct = method_breakdown.get("cache", 0)
|
|
@@ -261,20 +261,170 @@ class RichProgressDisplay:
|
|
|
261
261
|
qual_table.add_row("Cache Usage", f"{cache_pct:.1f}%")
|
|
262
262
|
qual_table.add_row("NLP Processing", f"{nlp_pct:.1f}%")
|
|
263
263
|
qual_table.add_row("LLM Processing", f"{llm_pct:.1f}%")
|
|
264
|
-
|
|
264
|
+
|
|
265
265
|
# LLM costs if available
|
|
266
266
|
if llm_stats.get("model_usage") == "available":
|
|
267
267
|
cost_tracking = llm_stats.get("cost_tracking", {})
|
|
268
268
|
total_cost = cost_tracking.get("total_cost", 0)
|
|
269
269
|
if total_cost > 0:
|
|
270
270
|
qual_table.add_row("LLM Cost", f"${total_cost:.4f}")
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
self.console.print(qual_table)
|
|
273
273
|
|
|
274
|
-
def
|
|
274
|
+
def show_llm_cost_summary(
|
|
275
|
+
self, cost_stats: dict[str, Any], identity_costs: Optional[dict[str, Any]] = None
|
|
276
|
+
) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Display comprehensive LLM usage and cost summary.
|
|
279
|
+
|
|
280
|
+
WHY: LLM usage can be expensive and users need visibility into:
|
|
281
|
+
- Token consumption by component (identity analysis, qualitative analysis)
|
|
282
|
+
- Cost breakdown and budget utilization
|
|
283
|
+
- Optimization suggestions to reduce costs
|
|
284
|
+
|
|
285
|
+
DESIGN DECISION: Separate method from qualitative stats because:
|
|
286
|
+
- Cost tracking spans multiple components (identity + qualitative)
|
|
287
|
+
- Users need this summary even when qualitative analysis is disabled
|
|
288
|
+
- Provides actionable cost optimization suggestions
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
cost_stats: Cost statistics from qualitative analysis
|
|
292
|
+
identity_costs: Optional cost statistics from identity analysis
|
|
293
|
+
"""
|
|
294
|
+
# Check if we have any cost data to display
|
|
295
|
+
has_qual_costs = cost_stats and cost_stats.get("total_cost", 0) > 0
|
|
296
|
+
has_identity_costs = identity_costs and identity_costs.get("total_cost", 0) > 0
|
|
297
|
+
|
|
298
|
+
if not (has_qual_costs or has_identity_costs):
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
self.console.print()
|
|
302
|
+
|
|
303
|
+
# Create main cost summary table
|
|
304
|
+
cost_table = Table(title="[bold]🤖 LLM Usage Summary[/bold]", box=box.ROUNDED)
|
|
305
|
+
cost_table.add_column("Component", style="cyan", width=20)
|
|
306
|
+
cost_table.add_column("Calls", style="yellow", width=8)
|
|
307
|
+
cost_table.add_column("Tokens", style="green", width=12)
|
|
308
|
+
cost_table.add_column("Cost", style="magenta", width=12)
|
|
309
|
+
|
|
310
|
+
total_calls = 0
|
|
311
|
+
total_tokens = 0
|
|
312
|
+
total_cost = 0.0
|
|
313
|
+
budget_info = {}
|
|
314
|
+
|
|
315
|
+
# Add identity analysis costs if available
|
|
316
|
+
if has_identity_costs:
|
|
317
|
+
identity_calls = identity_costs.get("total_calls", 0)
|
|
318
|
+
identity_tokens = identity_costs.get("total_tokens", 0)
|
|
319
|
+
identity_cost = identity_costs.get("total_cost", 0)
|
|
320
|
+
|
|
321
|
+
cost_table.add_row(
|
|
322
|
+
"Identity Analysis",
|
|
323
|
+
f"{identity_calls:,}",
|
|
324
|
+
f"{identity_tokens:,}",
|
|
325
|
+
f"${identity_cost:.4f}",
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
total_calls += identity_calls
|
|
329
|
+
total_tokens += identity_tokens
|
|
330
|
+
total_cost += identity_cost
|
|
331
|
+
|
|
332
|
+
# Add qualitative analysis costs if available
|
|
333
|
+
if has_qual_costs:
|
|
334
|
+
qual_calls = cost_stats.get("total_calls", 0)
|
|
335
|
+
qual_tokens = cost_stats.get("total_tokens", 0)
|
|
336
|
+
qual_cost = cost_stats.get("total_cost", 0)
|
|
337
|
+
|
|
338
|
+
cost_table.add_row(
|
|
339
|
+
"Qualitative Analysis", f"{qual_calls:,}", f"{qual_tokens:,}", f"${qual_cost:.4f}"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
total_calls += qual_calls
|
|
343
|
+
total_tokens += qual_tokens
|
|
344
|
+
total_cost += qual_cost
|
|
345
|
+
|
|
346
|
+
# Extract budget information (assuming it's in qualitative cost stats)
|
|
347
|
+
budget_info = {
|
|
348
|
+
"daily_budget": cost_stats.get("daily_budget", 5.0),
|
|
349
|
+
"daily_spend": cost_stats.get("daily_spend", total_cost),
|
|
350
|
+
"remaining_budget": cost_stats.get("remaining_budget", 5.0 - total_cost),
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# Add total row
|
|
354
|
+
if total_calls > 0:
|
|
355
|
+
cost_table.add_row(
|
|
356
|
+
"[bold]Total[/bold]",
|
|
357
|
+
f"[bold]{total_calls:,}[/bold]",
|
|
358
|
+
f"[bold]{total_tokens:,}[/bold]",
|
|
359
|
+
f"[bold]${total_cost:.4f}[/bold]",
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
self.console.print(cost_table)
|
|
363
|
+
|
|
364
|
+
# Display budget information if available
|
|
365
|
+
if budget_info:
|
|
366
|
+
daily_budget = budget_info.get("daily_budget", 5.0)
|
|
367
|
+
remaining = budget_info.get("remaining_budget", daily_budget - total_cost)
|
|
368
|
+
utilization = (total_cost / daily_budget) * 100 if daily_budget > 0 else 0
|
|
369
|
+
|
|
370
|
+
budget_text = f"Budget: ${daily_budget:.2f}, Remaining: ${remaining:.2f}, Utilization: {utilization:.1f}%"
|
|
371
|
+
|
|
372
|
+
# Color code based on utilization
|
|
373
|
+
if utilization >= 90:
|
|
374
|
+
budget_color = "red"
|
|
375
|
+
elif utilization >= 70:
|
|
376
|
+
budget_color = "yellow"
|
|
377
|
+
else:
|
|
378
|
+
budget_color = "green"
|
|
379
|
+
|
|
380
|
+
self.console.print(f" [{budget_color}]💰 {budget_text}[/{budget_color}]")
|
|
381
|
+
|
|
382
|
+
# Display cost optimization suggestions if we have detailed stats
|
|
383
|
+
suggestions = []
|
|
384
|
+
if has_qual_costs and "model_usage" in cost_stats:
|
|
385
|
+
model_usage = cost_stats.get("model_usage", {})
|
|
386
|
+
|
|
387
|
+
# Check for expensive model usage
|
|
388
|
+
expensive_models = ["anthropic/claude-3-opus", "openai/gpt-4"]
|
|
389
|
+
expensive_cost = sum(
|
|
390
|
+
model_usage.get(model, {}).get("cost", 0) for model in expensive_models
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
if expensive_cost > total_cost * 0.3:
|
|
394
|
+
suggestions.append(
|
|
395
|
+
"Consider using cheaper models (Claude Haiku, GPT-3.5) for routine tasks (save ~40%)"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Check for free model opportunities
|
|
399
|
+
free_usage = model_usage.get("meta-llama/llama-3.1-8b-instruct:free", {}).get(
|
|
400
|
+
"cost", -1
|
|
401
|
+
)
|
|
402
|
+
if free_usage == 0 and total_cost > 0.01: # If free models available but not used much
|
|
403
|
+
suggestions.append(
|
|
404
|
+
"Increase usage of free Llama models for simple classification tasks"
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Budget-based suggestions
|
|
408
|
+
if budget_info:
|
|
409
|
+
utilization = (total_cost / budget_info.get("daily_budget", 5.0)) * 100
|
|
410
|
+
if utilization > 80:
|
|
411
|
+
suggestions.append(
|
|
412
|
+
"Approaching daily budget limit - consider increasing NLP confidence threshold"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Display suggestions
|
|
416
|
+
if suggestions:
|
|
417
|
+
self.console.print()
|
|
418
|
+
self.console.print("[bold]💡 Cost Optimization Suggestions:[/bold]")
|
|
419
|
+
for suggestion in suggestions:
|
|
420
|
+
self.console.print(f" • {suggestion}")
|
|
421
|
+
|
|
422
|
+
self.console.print()
|
|
423
|
+
|
|
424
|
+
def show_reports_generated(self, output_dir: Path, report_files: list[str]) -> None:
|
|
275
425
|
"""
|
|
276
426
|
Display generated reports with file paths.
|
|
277
|
-
|
|
427
|
+
|
|
278
428
|
WHY: Users need to know where their reports were saved and what files
|
|
279
429
|
were generated. This provides clear next steps after analysis completes.
|
|
280
430
|
"""
|
|
@@ -282,14 +432,14 @@ class RichProgressDisplay:
|
|
|
282
432
|
return
|
|
283
433
|
|
|
284
434
|
self.console.print()
|
|
285
|
-
|
|
435
|
+
|
|
286
436
|
reports_panel = Panel(
|
|
287
437
|
f"[bold green]✓[/bold green] Reports exported to: [cyan]{output_dir}[/cyan]",
|
|
288
438
|
title="[bold]Generated Reports[/bold]",
|
|
289
439
|
box=box.ROUNDED,
|
|
290
440
|
)
|
|
291
441
|
self.console.print(reports_panel)
|
|
292
|
-
|
|
442
|
+
|
|
293
443
|
# List individual report files
|
|
294
444
|
for report_file in report_files:
|
|
295
445
|
self.console.print(f" • {report_file}")
|
|
@@ -297,7 +447,7 @@ class RichProgressDisplay:
|
|
|
297
447
|
def show_error(self, error_message: str, show_debug_hint: bool = True) -> None:
|
|
298
448
|
"""
|
|
299
449
|
Display error messages in a prominent format.
|
|
300
|
-
|
|
450
|
+
|
|
301
451
|
WHY: Errors need to be clearly visible and actionable. The panel format
|
|
302
452
|
makes them stand out while providing helpful guidance.
|
|
303
453
|
"""
|
|
@@ -307,7 +457,7 @@ class RichProgressDisplay:
|
|
|
307
457
|
box=box.HEAVY,
|
|
308
458
|
)
|
|
309
459
|
self.console.print(error_panel)
|
|
310
|
-
|
|
460
|
+
|
|
311
461
|
if show_debug_hint:
|
|
312
462
|
self.console.print("\n[dim]💡 Run with --debug for detailed error information[/dim]")
|
|
313
463
|
|
|
@@ -327,7 +477,7 @@ class RichProgressDisplay:
|
|
|
327
477
|
def print_status(self, message: str, status: str = "info") -> None:
|
|
328
478
|
"""
|
|
329
479
|
Print a status message with appropriate styling.
|
|
330
|
-
|
|
480
|
+
|
|
331
481
|
WHY: Provides consistent status messaging throughout the analysis process.
|
|
332
482
|
Different status types (info, success, warning, error) get appropriate styling.
|
|
333
483
|
"""
|
|
@@ -346,8 +496,8 @@ class RichProgressDisplay:
|
|
|
346
496
|
def create_rich_display() -> RichProgressDisplay:
|
|
347
497
|
"""
|
|
348
498
|
Factory function to create a Rich progress display.
|
|
349
|
-
|
|
499
|
+
|
|
350
500
|
WHY: Centralizes the creation of the display component and ensures
|
|
351
501
|
consistent configuration across the application.
|
|
352
502
|
"""
|
|
353
|
-
return RichProgressDisplay()
|
|
503
|
+
return RichProgressDisplay()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Configuration management for GitFlow Analytics.
|
|
2
|
+
|
|
3
|
+
This module provides configuration loading, validation, and management
|
|
4
|
+
for the GitFlow Analytics tool. It has been refactored into focused
|
|
5
|
+
sub-modules while maintaining backward compatibility.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Re-export main interfaces for backward compatibility
|
|
9
|
+
from .loader import ConfigLoader
|
|
10
|
+
from .schema import (
|
|
11
|
+
AnalysisConfig,
|
|
12
|
+
BranchAnalysisConfig,
|
|
13
|
+
CacheConfig,
|
|
14
|
+
CommitClassificationConfig,
|
|
15
|
+
Config,
|
|
16
|
+
GitHubConfig,
|
|
17
|
+
JIRAConfig,
|
|
18
|
+
JIRAIntegrationConfig,
|
|
19
|
+
LLMClassificationConfig,
|
|
20
|
+
MLCategorization,
|
|
21
|
+
OutputConfig,
|
|
22
|
+
PMIntegrationConfig,
|
|
23
|
+
PMPlatformConfig,
|
|
24
|
+
RepositoryConfig,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ConfigLoader",
|
|
29
|
+
"Config",
|
|
30
|
+
"RepositoryConfig",
|
|
31
|
+
"GitHubConfig",
|
|
32
|
+
"AnalysisConfig",
|
|
33
|
+
"OutputConfig",
|
|
34
|
+
"CacheConfig",
|
|
35
|
+
"JIRAConfig",
|
|
36
|
+
"JIRAIntegrationConfig",
|
|
37
|
+
"PMPlatformConfig",
|
|
38
|
+
"PMIntegrationConfig",
|
|
39
|
+
"MLCategorization",
|
|
40
|
+
"LLMClassificationConfig",
|
|
41
|
+
"CommitClassificationConfig",
|
|
42
|
+
"BranchAnalysisConfig",
|
|
43
|
+
]
|