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.
- gitflow_analytics/__init__.py +11 -11
- gitflow_analytics/_version.py +2 -2
- 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 +4490 -378
- gitflow_analytics/cli_rich.py +503 -0
- gitflow_analytics/config/__init__.py +43 -0
- gitflow_analytics/config/errors.py +261 -0
- gitflow_analytics/config/loader.py +904 -0
- gitflow_analytics/config/profiles.py +264 -0
- gitflow_analytics/config/repository.py +124 -0
- gitflow_analytics/config/schema.py +441 -0
- gitflow_analytics/config/validator.py +154 -0
- gitflow_analytics/config.py +44 -398
- gitflow_analytics/core/analyzer.py +1320 -172
- gitflow_analytics/core/branch_mapper.py +132 -132
- gitflow_analytics/core/cache.py +1554 -175
- gitflow_analytics/core/data_fetcher.py +1193 -0
- gitflow_analytics/core/identity.py +571 -185
- 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/base.py +13 -11
- gitflow_analytics/extractors/ml_tickets.py +1100 -0
- gitflow_analytics/extractors/story_points.py +77 -59
- gitflow_analytics/extractors/tickets.py +841 -89
- 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 +258 -87
- gitflow_analytics/integrations/jira_integration.py +572 -123
- gitflow_analytics/integrations/orchestrator.py +206 -82
- gitflow_analytics/metrics/activity_scoring.py +322 -0
- gitflow_analytics/metrics/branch_health.py +470 -0
- gitflow_analytics/metrics/dora.py +542 -179
- gitflow_analytics/models/database.py +986 -59
- 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 +29 -0
- gitflow_analytics/qualitative/chatgpt_analyzer.py +259 -0
- gitflow_analytics/qualitative/classifiers/__init__.py +13 -0
- gitflow_analytics/qualitative/classifiers/change_type.py +742 -0
- gitflow_analytics/qualitative/classifiers/domain_classifier.py +506 -0
- gitflow_analytics/qualitative/classifiers/intent_analyzer.py +535 -0
- 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 +438 -0
- gitflow_analytics/qualitative/core/__init__.py +13 -0
- gitflow_analytics/qualitative/core/llm_fallback.py +657 -0
- gitflow_analytics/qualitative/core/nlp_engine.py +382 -0
- gitflow_analytics/qualitative/core/pattern_cache.py +479 -0
- gitflow_analytics/qualitative/core/processor.py +673 -0
- gitflow_analytics/qualitative/enhanced_analyzer.py +2236 -0
- gitflow_analytics/qualitative/example_enhanced_usage.py +420 -0
- gitflow_analytics/qualitative/models/__init__.py +25 -0
- gitflow_analytics/qualitative/models/schemas.py +306 -0
- gitflow_analytics/qualitative/utils/__init__.py +13 -0
- gitflow_analytics/qualitative/utils/batch_processor.py +339 -0
- gitflow_analytics/qualitative/utils/cost_tracker.py +345 -0
- gitflow_analytics/qualitative/utils/metrics.py +361 -0
- gitflow_analytics/qualitative/utils/text_processing.py +285 -0
- gitflow_analytics/reports/__init__.py +100 -0
- gitflow_analytics/reports/analytics_writer.py +550 -18
- 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 +1700 -216
- 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 +2289 -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 +5 -0
- gitflow_analytics/tui/app.py +724 -0
- gitflow_analytics/tui/screens/__init__.py +8 -0
- gitflow_analytics/tui/screens/analysis_progress_screen.py +496 -0
- gitflow_analytics/tui/screens/configuration_screen.py +523 -0
- gitflow_analytics/tui/screens/loading_screen.py +348 -0
- gitflow_analytics/tui/screens/main_screen.py +321 -0
- gitflow_analytics/tui/screens/results_screen.py +722 -0
- gitflow_analytics/tui/widgets/__init__.py +7 -0
- gitflow_analytics/tui/widgets/data_table.py +255 -0
- gitflow_analytics/tui/widgets/export_modal.py +301 -0
- gitflow_analytics/tui/widgets/progress_widget.py +187 -0
- gitflow_analytics-1.3.6.dist-info/METADATA +1015 -0
- gitflow_analytics-1.3.6.dist-info/RECORD +122 -0
- gitflow_analytics-1.0.1.dist-info/METADATA +0 -463
- gitflow_analytics-1.0.1.dist-info/RECORD +0 -31
- {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/WHEEL +0 -0
- {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/entry_points.txt +0 -0
- {gitflow_analytics-1.0.1.dist-info → gitflow_analytics-1.3.6.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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[
|
|
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
|
|
14
|
-
r
|
|
15
|
-
r
|
|
16
|
-
r
|
|
17
|
-
r
|
|
18
|
-
r
|
|
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(
|
|
42
|
-
|
|
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(
|
|
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(
|
|
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
|
|
56
|
-
points = self.extract_from_text(pr_data[
|
|
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(
|
|
79
|
-
|
|
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(
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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(
|
|
108
|
+
pr_points = pr.get("story_points", 0)
|
|
102
109
|
if pr_points:
|
|
103
|
-
results[
|
|
104
|
-
results[
|
|
110
|
+
results["pr_story_points"] += pr_points
|
|
111
|
+
results["total_story_points"] += pr_points
|
|
105
112
|
else:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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[
|
|
117
|
-
results[
|
|
118
|
-
|
|
123
|
+
results["commit_story_points"] += commit_points
|
|
124
|
+
results["total_story_points"] += commit_points
|
|
125
|
+
|
|
119
126
|
# Track significant orphan commits
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|