gitflow-analytics 1.0.1__py3-none-any.whl → 1.3.6__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 (119) hide show
  1. gitflow_analytics/__init__.py +11 -11
  2. gitflow_analytics/_version.py +2 -2
  3. gitflow_analytics/classification/__init__.py +31 -0
  4. gitflow_analytics/classification/batch_classifier.py +752 -0
  5. gitflow_analytics/classification/classifier.py +464 -0
  6. gitflow_analytics/classification/feature_extractor.py +725 -0
  7. gitflow_analytics/classification/linguist_analyzer.py +574 -0
  8. gitflow_analytics/classification/model.py +455 -0
  9. gitflow_analytics/cli.py +4490 -378
  10. gitflow_analytics/cli_rich.py +503 -0
  11. gitflow_analytics/config/__init__.py +43 -0
  12. gitflow_analytics/config/errors.py +261 -0
  13. gitflow_analytics/config/loader.py +904 -0
  14. gitflow_analytics/config/profiles.py +264 -0
  15. gitflow_analytics/config/repository.py +124 -0
  16. gitflow_analytics/config/schema.py +441 -0
  17. gitflow_analytics/config/validator.py +154 -0
  18. gitflow_analytics/config.py +44 -398
  19. gitflow_analytics/core/analyzer.py +1320 -172
  20. gitflow_analytics/core/branch_mapper.py +132 -132
  21. gitflow_analytics/core/cache.py +1554 -175
  22. gitflow_analytics/core/data_fetcher.py +1193 -0
  23. gitflow_analytics/core/identity.py +571 -185
  24. gitflow_analytics/core/metrics_storage.py +526 -0
  25. gitflow_analytics/core/progress.py +372 -0
  26. gitflow_analytics/core/schema_version.py +269 -0
  27. gitflow_analytics/extractors/base.py +13 -11
  28. gitflow_analytics/extractors/ml_tickets.py +1100 -0
  29. gitflow_analytics/extractors/story_points.py +77 -59
  30. gitflow_analytics/extractors/tickets.py +841 -89
  31. gitflow_analytics/identity_llm/__init__.py +6 -0
  32. gitflow_analytics/identity_llm/analysis_pass.py +231 -0
  33. gitflow_analytics/identity_llm/analyzer.py +464 -0
  34. gitflow_analytics/identity_llm/models.py +76 -0
  35. gitflow_analytics/integrations/github_integration.py +258 -87
  36. gitflow_analytics/integrations/jira_integration.py +572 -123
  37. gitflow_analytics/integrations/orchestrator.py +206 -82
  38. gitflow_analytics/metrics/activity_scoring.py +322 -0
  39. gitflow_analytics/metrics/branch_health.py +470 -0
  40. gitflow_analytics/metrics/dora.py +542 -179
  41. gitflow_analytics/models/database.py +986 -59
  42. gitflow_analytics/pm_framework/__init__.py +115 -0
  43. gitflow_analytics/pm_framework/adapters/__init__.py +50 -0
  44. gitflow_analytics/pm_framework/adapters/jira_adapter.py +1845 -0
  45. gitflow_analytics/pm_framework/base.py +406 -0
  46. gitflow_analytics/pm_framework/models.py +211 -0
  47. gitflow_analytics/pm_framework/orchestrator.py +652 -0
  48. gitflow_analytics/pm_framework/registry.py +333 -0
  49. gitflow_analytics/qualitative/__init__.py +29 -0
  50. gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
  51. gitflow_analytics/qualitative/classifiers/__init__.py +13 -0
  52. gitflow_analytics/qualitative/classifiers/change_type.py +742 -0
  53. gitflow_analytics/qualitative/classifiers/domain_classifier.py +506 -0
  54. gitflow_analytics/qualitative/classifiers/intent_analyzer.py +535 -0
  55. gitflow_analytics/qualitative/classifiers/llm/__init__.py +35 -0
  56. gitflow_analytics/qualitative/classifiers/llm/base.py +193 -0
  57. gitflow_analytics/qualitative/classifiers/llm/batch_processor.py +383 -0
  58. gitflow_analytics/qualitative/classifiers/llm/cache.py +479 -0
  59. gitflow_analytics/qualitative/classifiers/llm/cost_tracker.py +435 -0
  60. gitflow_analytics/qualitative/classifiers/llm/openai_client.py +403 -0
  61. gitflow_analytics/qualitative/classifiers/llm/prompts.py +373 -0
  62. gitflow_analytics/qualitative/classifiers/llm/response_parser.py +287 -0
  63. gitflow_analytics/qualitative/classifiers/llm_commit_classifier.py +607 -0
  64. gitflow_analytics/qualitative/classifiers/risk_analyzer.py +438 -0
  65. gitflow_analytics/qualitative/core/__init__.py +13 -0
  66. gitflow_analytics/qualitative/core/llm_fallback.py +657 -0
  67. gitflow_analytics/qualitative/core/nlp_engine.py +382 -0
  68. gitflow_analytics/qualitative/core/pattern_cache.py +479 -0
  69. gitflow_analytics/qualitative/core/processor.py +673 -0
  70. gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
  71. gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
  72. gitflow_analytics/qualitative/models/__init__.py +25 -0
  73. gitflow_analytics/qualitative/models/schemas.py +306 -0
  74. gitflow_analytics/qualitative/utils/__init__.py +13 -0
  75. gitflow_analytics/qualitative/utils/batch_processor.py +339 -0
  76. gitflow_analytics/qualitative/utils/cost_tracker.py +345 -0
  77. gitflow_analytics/qualitative/utils/metrics.py +361 -0
  78. gitflow_analytics/qualitative/utils/text_processing.py +285 -0
  79. gitflow_analytics/reports/__init__.py +100 -0
  80. gitflow_analytics/reports/analytics_writer.py +550 -18
  81. gitflow_analytics/reports/base.py +648 -0
  82. gitflow_analytics/reports/branch_health_writer.py +322 -0
  83. gitflow_analytics/reports/classification_writer.py +924 -0
  84. gitflow_analytics/reports/cli_integration.py +427 -0
  85. gitflow_analytics/reports/csv_writer.py +1700 -216
  86. gitflow_analytics/reports/data_models.py +504 -0
  87. gitflow_analytics/reports/database_report_generator.py +427 -0
  88. gitflow_analytics/reports/example_usage.py +344 -0
  89. gitflow_analytics/reports/factory.py +499 -0
  90. gitflow_analytics/reports/formatters.py +698 -0
  91. gitflow_analytics/reports/html_generator.py +1116 -0
  92. gitflow_analytics/reports/interfaces.py +489 -0
  93. gitflow_analytics/reports/json_exporter.py +2770 -0
  94. gitflow_analytics/reports/narrative_writer.py +2289 -158
  95. gitflow_analytics/reports/story_point_correlation.py +1144 -0
  96. gitflow_analytics/reports/weekly_trends_writer.py +389 -0
  97. gitflow_analytics/training/__init__.py +5 -0
  98. gitflow_analytics/training/model_loader.py +377 -0
  99. gitflow_analytics/training/pipeline.py +550 -0
  100. gitflow_analytics/tui/__init__.py +5 -0
  101. gitflow_analytics/tui/app.py +724 -0
  102. gitflow_analytics/tui/screens/__init__.py +8 -0
  103. gitflow_analytics/tui/screens/analysis_progress_screen.py +496 -0
  104. gitflow_analytics/tui/screens/configuration_screen.py +523 -0
  105. gitflow_analytics/tui/screens/loading_screen.py +348 -0
  106. gitflow_analytics/tui/screens/main_screen.py +321 -0
  107. gitflow_analytics/tui/screens/results_screen.py +722 -0
  108. gitflow_analytics/tui/widgets/__init__.py +7 -0
  109. gitflow_analytics/tui/widgets/data_table.py +255 -0
  110. gitflow_analytics/tui/widgets/export_modal.py +301 -0
  111. gitflow_analytics/tui/widgets/progress_widget.py +187 -0
  112. gitflow_analytics-1.3.6.dist-info/METADATA +1015 -0
  113. gitflow_analytics-1.3.6.dist-info/RECORD +122 -0
  114. gitflow_analytics-1.0.1.dist-info/METADATA +0 -463
  115. gitflow_analytics-1.0.1.dist-info/RECORD +0 -31
  116. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/WHEEL +0 -0
  117. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/entry_points.txt +0 -0
  118. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/licenses/LICENSE +0 -0
  119. {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/top_level.txt +0 -0
@@ -1,30 +1,31 @@
1
1
  """Story point extraction from commits and pull requests."""
2
+
2
3
  import re
3
- from typing import Any, Dict, List, Optional
4
+ from typing import Any, Optional
4
5
 
5
6
 
6
7
  class StoryPointExtractor:
7
8
  """Extract story points from text using configurable patterns."""
8
-
9
- def __init__(self, patterns: Optional[List[str]] = None):
9
+
10
+ def __init__(self, patterns: Optional[list[str]] = None):
10
11
  """Initialize with extraction patterns."""
11
12
  if patterns is None:
12
13
  patterns = [
13
- r'(?:story\s*points?|sp|pts?)\s*[:=]\s*(\d+)', # SP: 5, story points = 3
14
- r'\[(\d+)\s*(?:sp|pts?)\]', # [3sp], [5 pts]
15
- r'#(\d+)sp', # #3sp
16
- r'estimate:\s*(\d+)', # estimate: 5
17
- r'\bSP(\d+)\b', # SP5, SP13
18
- r'points?:\s*(\d+)', # points: 8
14
+ r"(?:story\s*points?|sp|pts?)\s*[:=]\s*(\d+)", # SP: 5, story points = 3
15
+ r"\[(\d+)\s*(?:sp|pts?)\]", # [3sp], [5 pts]
16
+ r"#(\d+)sp", # #3sp
17
+ r"estimate:\s*(\d+)", # estimate: 5
18
+ r"\bSP(\d+)\b", # SP5, SP13
19
+ r"points?:\s*(\d+)", # points: 8
19
20
  ]
20
-
21
+
21
22
  self.patterns = [re.compile(pattern, re.IGNORECASE) for pattern in patterns]
22
-
23
+
23
24
  def extract_from_text(self, text: str) -> Optional[int]:
24
25
  """Extract story points from text."""
25
26
  if not text:
26
27
  return None
27
-
28
+
28
29
  for pattern in self.patterns:
29
30
  match = pattern.search(text)
30
31
  if match:
@@ -35,28 +36,29 @@ class StoryPointExtractor:
35
36
  return points
36
37
  except (ValueError, IndexError):
37
38
  continue
38
-
39
+
39
40
  return None
40
-
41
- def extract_from_pr(self, pr_data: Dict[str, Any],
42
- commit_messages: Optional[List[str]] = None) -> Optional[int]:
41
+
42
+ def extract_from_pr(
43
+ self, pr_data: dict[str, Any], commit_messages: Optional[list[str]] = None
44
+ ) -> Optional[int]:
43
45
  """Extract story points from PR with fallback to commits."""
44
46
  # Try PR description first (most authoritative)
45
- points = self.extract_from_text(pr_data.get('description', ''))
47
+ points = self.extract_from_text(pr_data.get("description", ""))
46
48
  if points:
47
49
  return points
48
-
50
+
49
51
  # Try PR title
50
- points = self.extract_from_text(pr_data.get('title', ''))
52
+ points = self.extract_from_text(pr_data.get("title", ""))
51
53
  if points:
52
54
  return points
53
-
55
+
54
56
  # Try PR body (if different from description)
55
- if 'body' in pr_data:
56
- points = self.extract_from_text(pr_data['body'])
57
+ if "body" in pr_data:
58
+ points = self.extract_from_text(pr_data["body"])
57
59
  if points:
58
60
  return points
59
-
61
+
60
62
  # Fallback to commit messages
61
63
  if commit_messages:
62
64
  commit_points = []
@@ -64,65 +66,81 @@ class StoryPointExtractor:
64
66
  points = self.extract_from_text(message)
65
67
  if points:
66
68
  commit_points.append(points)
67
-
69
+
68
70
  if commit_points:
69
71
  # Use the most common value or max if no consensus
70
72
  from collections import Counter
73
+
71
74
  point_counts = Counter(commit_points)
72
75
  most_common = point_counts.most_common(1)
73
76
  if most_common:
74
77
  return most_common[0][0]
75
-
78
+
76
79
  return None
77
-
78
- def aggregate_story_points(self, prs: List[Dict[str, Any]],
79
- commits: List[Dict[str, Any]]) -> Dict[str, Any]:
80
+
81
+ def aggregate_story_points(
82
+ self, prs: list[dict[str, Any]], commits: list[dict[str, Any]]
83
+ ) -> dict[str, Any]:
80
84
  """Aggregate story points from PRs and commits."""
81
85
  # Map commits to PRs
82
86
  pr_by_commit = {}
83
87
  for pr in prs:
84
- for commit_hash in pr.get('commit_hashes', []):
88
+ for commit_hash in pr.get("commit_hashes", []):
85
89
  pr_by_commit[commit_hash] = pr
86
-
90
+
87
91
  # Track which commits are associated with PRs
88
92
  pr_commits = set(pr_by_commit.keys())
89
-
93
+
90
94
  # Aggregate results
95
+ orphan_commits: list[dict[str, Any]] = []
96
+ unestimated_prs: list[dict[str, Any]] = []
97
+
91
98
  results = {
92
- 'total_story_points': 0,
93
- 'pr_story_points': 0,
94
- 'commit_story_points': 0,
95
- 'orphan_commits': [], # Commits without PRs
96
- 'unestimated_prs': [] # PRs without story points
99
+ "total_story_points": 0,
100
+ "pr_story_points": 0,
101
+ "commit_story_points": 0,
102
+ "orphan_commits": orphan_commits, # Commits without PRs
103
+ "unestimated_prs": unestimated_prs, # PRs without story points
97
104
  }
98
-
105
+
99
106
  # Process PRs
100
107
  for pr in prs:
101
- pr_points = pr.get('story_points', 0)
108
+ pr_points = pr.get("story_points", 0)
102
109
  if pr_points:
103
- results['pr_story_points'] += pr_points
104
- results['total_story_points'] += pr_points
110
+ results["pr_story_points"] += pr_points
111
+ results["total_story_points"] += pr_points
105
112
  else:
106
- results['unestimated_prs'].append({
107
- 'number': pr['number'],
108
- 'title': pr['title']
109
- })
110
-
113
+ unestimated_prs.append(
114
+ {"number": pr.get("number", 0), "title": pr.get("title", "")}
115
+ )
116
+
111
117
  # Process commits not in PRs
112
118
  for commit in commits:
113
- if commit['hash'] not in pr_commits:
114
- commit_points = commit.get('story_points', 0)
119
+ commit_hash = commit.get("hash", "")
120
+ if commit_hash not in pr_commits:
121
+ commit_points = commit.get("story_points", 0)
115
122
  if commit_points:
116
- results['commit_story_points'] += commit_points
117
- results['total_story_points'] += commit_points
118
-
123
+ results["commit_story_points"] += commit_points
124
+ results["total_story_points"] += commit_points
125
+
119
126
  # Track significant orphan commits
120
- if commit['files_changed'] > 5 or commit['insertions'] > 100:
121
- results['orphan_commits'].append({
122
- 'hash': commit['hash'][:7],
123
- 'message': commit['message'].split('\n')[0][:80],
124
- 'story_points': commit_points,
125
- 'files_changed': commit['files_changed']
126
- })
127
-
128
- return results
127
+ files_changed = commit.get(
128
+ "files_changed_count",
129
+ (
130
+ commit.get("files_changed", 0)
131
+ if isinstance(commit.get("files_changed"), int)
132
+ else len(commit.get("files_changed", []))
133
+ ),
134
+ )
135
+ insertions = commit.get("insertions", 0)
136
+ if files_changed > 5 or insertions > 100:
137
+ orphan_commits.append(
138
+ {
139
+ "hash": commit.get("hash", "")[:7],
140
+ "message": commit.get("message", "").split("\n")[0][:80],
141
+ "story_points": commit_points,
142
+ "files_changed": files_changed,
143
+ }
144
+ )
145
+
146
+ return results