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.
Files changed (116) hide show
  1. gitflow_analytics/_version.py +1 -1
  2. gitflow_analytics/classification/__init__.py +31 -0
  3. gitflow_analytics/classification/batch_classifier.py +752 -0
  4. gitflow_analytics/classification/classifier.py +464 -0
  5. gitflow_analytics/classification/feature_extractor.py +725 -0
  6. gitflow_analytics/classification/linguist_analyzer.py +574 -0
  7. gitflow_analytics/classification/model.py +455 -0
  8. gitflow_analytics/cli.py +4158 -350
  9. gitflow_analytics/cli_rich.py +198 -48
  10. gitflow_analytics/config/__init__.py +43 -0
  11. gitflow_analytics/config/errors.py +261 -0
  12. gitflow_analytics/config/loader.py +905 -0
  13. gitflow_analytics/config/profiles.py +264 -0
  14. gitflow_analytics/config/repository.py +124 -0
  15. gitflow_analytics/config/schema.py +444 -0
  16. gitflow_analytics/config/validator.py +154 -0
  17. gitflow_analytics/config.py +44 -508
  18. gitflow_analytics/core/analyzer.py +1209 -98
  19. gitflow_analytics/core/cache.py +1337 -29
  20. gitflow_analytics/core/data_fetcher.py +1285 -0
  21. gitflow_analytics/core/identity.py +363 -14
  22. gitflow_analytics/core/metrics_storage.py +526 -0
  23. gitflow_analytics/core/progress.py +372 -0
  24. gitflow_analytics/core/schema_version.py +269 -0
  25. gitflow_analytics/extractors/ml_tickets.py +1100 -0
  26. gitflow_analytics/extractors/story_points.py +8 -1
  27. gitflow_analytics/extractors/tickets.py +749 -11
  28. gitflow_analytics/identity_llm/__init__.py +6 -0
  29. gitflow_analytics/identity_llm/analysis_pass.py +231 -0
  30. gitflow_analytics/identity_llm/analyzer.py +464 -0
  31. gitflow_analytics/identity_llm/models.py +76 -0
  32. gitflow_analytics/integrations/github_integration.py +175 -11
  33. gitflow_analytics/integrations/jira_integration.py +461 -24
  34. gitflow_analytics/integrations/orchestrator.py +124 -1
  35. gitflow_analytics/metrics/activity_scoring.py +322 -0
  36. gitflow_analytics/metrics/branch_health.py +470 -0
  37. gitflow_analytics/metrics/dora.py +379 -20
  38. gitflow_analytics/models/database.py +843 -53
  39. gitflow_analytics/pm_framework/__init__.py +115 -0
  40. gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
  41. gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
  42. gitflow_analytics/pm_framework/base.py +406 -0
  43. gitflow_analytics/pm_framework/models.py +211 -0
  44. gitflow_analytics/pm_framework/orchestrator.py +652 -0
  45. gitflow_analytics/pm_framework/registry.py +333 -0
  46. gitflow_analytics/qualitative/__init__.py +9 -10
  47. gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
  48. gitflow_analytics/qualitative/classifiers/__init__.py +3 -3
  49. gitflow_analytics/qualitative/classifiers/change_type.py +518 -244
  50. gitflow_analytics/qualitative/classifiers/domain_classifier.py +272 -165
  51. gitflow_analytics/qualitative/classifiers/intent_analyzer.py +321 -222
  52. gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
  53. gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
  54. gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
  55. gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
  56. gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
  57. gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
  58. gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
  59. gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
  60. gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
  61. gitflow_analytics/qualitative/classifiers/risk_analyzer.py +215 -189
  62. gitflow_analytics/qualitative/core/__init__.py +4 -4
  63. gitflow_analytics/qualitative/core/llm_fallback.py +239 -235
  64. gitflow_analytics/qualitative/core/nlp_engine.py +157 -148
  65. gitflow_analytics/qualitative/core/pattern_cache.py +214 -192
  66. gitflow_analytics/qualitative/core/processor.py +381 -248
  67. gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
  68. gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
  69. gitflow_analytics/qualitative/models/__init__.py +7 -7
  70. gitflow_analytics/qualitative/models/schemas.py +155 -121
  71. gitflow_analytics/qualitative/utils/__init__.py +4 -4
  72. gitflow_analytics/qualitative/utils/batch_processor.py +136 -123
  73. gitflow_analytics/qualitative/utils/cost_tracker.py +142 -140
  74. gitflow_analytics/qualitative/utils/metrics.py +172 -158
  75. gitflow_analytics/qualitative/utils/text_processing.py +146 -104
  76. gitflow_analytics/reports/__init__.py +100 -0
  77. gitflow_analytics/reports/analytics_writer.py +539 -14
  78. gitflow_analytics/reports/base.py +648 -0
  79. gitflow_analytics/reports/branch_health_writer.py +322 -0
  80. gitflow_analytics/reports/classification_writer.py +924 -0
  81. gitflow_analytics/reports/cli_integration.py +427 -0
  82. gitflow_analytics/reports/csv_writer.py +1676 -212
  83. gitflow_analytics/reports/data_models.py +504 -0
  84. gitflow_analytics/reports/database_report_generator.py +427 -0
  85. gitflow_analytics/reports/example_usage.py +344 -0
  86. gitflow_analytics/reports/factory.py +499 -0
  87. gitflow_analytics/reports/formatters.py +698 -0
  88. gitflow_analytics/reports/html_generator.py +1116 -0
  89. gitflow_analytics/reports/interfaces.py +489 -0
  90. gitflow_analytics/reports/json_exporter.py +2770 -0
  91. gitflow_analytics/reports/narrative_writer.py +2287 -158
  92. gitflow_analytics/reports/story_point_correlation.py +1144 -0
  93. gitflow_analytics/reports/weekly_trends_writer.py +389 -0
  94. gitflow_analytics/training/__init__.py +5 -0
  95. gitflow_analytics/training/model_loader.py +377 -0
  96. gitflow_analytics/training/pipeline.py +550 -0
  97. gitflow_analytics/tui/__init__.py +1 -1
  98. gitflow_analytics/tui/app.py +129 -126
  99. gitflow_analytics/tui/screens/__init__.py +3 -3
  100. gitflow_analytics/tui/screens/analysis_progress_screen.py +188 -179
  101. gitflow_analytics/tui/screens/configuration_screen.py +154 -178
  102. gitflow_analytics/tui/screens/loading_screen.py +100 -110
  103. gitflow_analytics/tui/screens/main_screen.py +89 -72
  104. gitflow_analytics/tui/screens/results_screen.py +305 -281
  105. gitflow_analytics/tui/widgets/__init__.py +2 -2
  106. gitflow_analytics/tui/widgets/data_table.py +67 -69
  107. gitflow_analytics/tui/widgets/export_modal.py +76 -76
  108. gitflow_analytics/tui/widgets/progress_widget.py +41 -46
  109. gitflow_analytics-1.3.11.dist-info/METADATA +1015 -0
  110. gitflow_analytics-1.3.11.dist-info/RECORD +122 -0
  111. gitflow_analytics-1.0.3.dist-info/METADATA +0 -490
  112. gitflow_analytics-1.0.3.dist-info/RECORD +0 -62
  113. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/WHEEL +0 -0
  114. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/entry_points.txt +0 -0
  115. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/licenses/LICENSE +0 -0
  116. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.11.dist-info}/top_level.txt +0 -0
@@ -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, Dict, List, Optional
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: Dict[str, TaskID] = {}
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(self, name: str, advance: int = 1, description: Optional[str] = None) -> None:
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: List[Dict[str, Any]]) -> None:
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: Dict[str, Any]) -> None:
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: Dict[str, Any]) -> None:
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 show_reports_generated(self, output_dir: Path, report_files: List[str]) -> None:
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
+ ]