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
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Standardized data models for reports.
|
|
2
|
+
|
|
3
|
+
This module defines the data structures used throughout the report
|
|
4
|
+
generation system, ensuring consistency and type safety.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import date, datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommitType(Enum):
|
|
14
|
+
"""Types of commits."""
|
|
15
|
+
|
|
16
|
+
FEATURE = "feature"
|
|
17
|
+
BUG_FIX = "bug_fix"
|
|
18
|
+
REFACTOR = "refactor"
|
|
19
|
+
DOCUMENTATION = "documentation"
|
|
20
|
+
TEST = "test"
|
|
21
|
+
MAINTENANCE = "maintenance"
|
|
22
|
+
STYLE = "style"
|
|
23
|
+
BUILD = "build"
|
|
24
|
+
OTHER = "other"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WorkStyle(Enum):
|
|
28
|
+
"""Developer work style categories."""
|
|
29
|
+
|
|
30
|
+
CONSISTENT = "consistent"
|
|
31
|
+
BURST = "burst"
|
|
32
|
+
IRREGULAR = "irregular"
|
|
33
|
+
DECLINING = "declining"
|
|
34
|
+
GROWING = "growing"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class DeveloperIdentity:
|
|
39
|
+
"""Developer identity information."""
|
|
40
|
+
|
|
41
|
+
canonical_id: str
|
|
42
|
+
primary_email: str
|
|
43
|
+
primary_name: str
|
|
44
|
+
aliases: List[str] = field(default_factory=list)
|
|
45
|
+
emails: List[str] = field(default_factory=list)
|
|
46
|
+
is_bot: bool = False
|
|
47
|
+
display_name: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
def get_display_name(self) -> str:
|
|
50
|
+
"""Get the display name for the developer."""
|
|
51
|
+
return self.display_name or self.primary_name
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class CommitData:
|
|
56
|
+
"""Standardized commit data structure."""
|
|
57
|
+
|
|
58
|
+
hash: str
|
|
59
|
+
author_email: str
|
|
60
|
+
author_name: str
|
|
61
|
+
timestamp: datetime
|
|
62
|
+
message: str
|
|
63
|
+
|
|
64
|
+
# Identity
|
|
65
|
+
canonical_id: Optional[str] = None
|
|
66
|
+
|
|
67
|
+
# Repository info
|
|
68
|
+
project_key: Optional[str] = None
|
|
69
|
+
repository: Optional[str] = None
|
|
70
|
+
branch: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
# Metrics
|
|
73
|
+
insertions: int = 0
|
|
74
|
+
deletions: int = 0
|
|
75
|
+
files_changed: int = 0
|
|
76
|
+
|
|
77
|
+
# Filtered metrics (excluding certain files)
|
|
78
|
+
filtered_insertions: Optional[int] = None
|
|
79
|
+
filtered_deletions: Optional[int] = None
|
|
80
|
+
|
|
81
|
+
# Classification
|
|
82
|
+
commit_type: Optional[CommitType] = None
|
|
83
|
+
is_merge: bool = False
|
|
84
|
+
|
|
85
|
+
# Ticket references
|
|
86
|
+
ticket_ids: List[str] = field(default_factory=list)
|
|
87
|
+
has_ticket: bool = False
|
|
88
|
+
|
|
89
|
+
# Story points
|
|
90
|
+
story_points: Optional[float] = None
|
|
91
|
+
|
|
92
|
+
# Additional metadata
|
|
93
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
94
|
+
|
|
95
|
+
def get_line_changes(self, use_filtered: bool = True) -> tuple[int, int]:
|
|
96
|
+
"""Get line changes (insertions, deletions).
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
use_filtered: Whether to use filtered metrics if available
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tuple of (insertions, deletions)
|
|
103
|
+
"""
|
|
104
|
+
if use_filtered and self.filtered_insertions is not None:
|
|
105
|
+
return (self.filtered_insertions, self.filtered_deletions or 0)
|
|
106
|
+
return (self.insertions, self.deletions)
|
|
107
|
+
|
|
108
|
+
def get_total_lines(self, use_filtered: bool = True) -> int:
|
|
109
|
+
"""Get total lines changed.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
use_filtered: Whether to use filtered metrics if available
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Total lines changed
|
|
116
|
+
"""
|
|
117
|
+
ins, dels = self.get_line_changes(use_filtered)
|
|
118
|
+
return ins + dels
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class PullRequestData:
|
|
123
|
+
"""Standardized pull request data structure."""
|
|
124
|
+
|
|
125
|
+
id: Union[int, str]
|
|
126
|
+
title: str
|
|
127
|
+
author: str
|
|
128
|
+
created_at: datetime
|
|
129
|
+
merged_at: Optional[datetime] = None
|
|
130
|
+
closed_at: Optional[datetime] = None
|
|
131
|
+
|
|
132
|
+
# State
|
|
133
|
+
state: str = "open" # open, closed, merged
|
|
134
|
+
is_merged: bool = False
|
|
135
|
+
|
|
136
|
+
# Repository info
|
|
137
|
+
project_key: Optional[str] = None
|
|
138
|
+
repository: Optional[str] = None
|
|
139
|
+
base_branch: Optional[str] = None
|
|
140
|
+
head_branch: Optional[str] = None
|
|
141
|
+
|
|
142
|
+
# Metrics
|
|
143
|
+
commits_count: int = 0
|
|
144
|
+
additions: int = 0
|
|
145
|
+
deletions: int = 0
|
|
146
|
+
files_changed: int = 0
|
|
147
|
+
comments_count: int = 0
|
|
148
|
+
review_comments_count: int = 0
|
|
149
|
+
|
|
150
|
+
# Review info
|
|
151
|
+
reviewers: List[str] = field(default_factory=list)
|
|
152
|
+
approved_by: List[str] = field(default_factory=list)
|
|
153
|
+
|
|
154
|
+
# Time metrics
|
|
155
|
+
time_to_merge_hours: Optional[float] = None
|
|
156
|
+
time_to_first_review_hours: Optional[float] = None
|
|
157
|
+
|
|
158
|
+
# Labels and metadata
|
|
159
|
+
labels: List[str] = field(default_factory=list)
|
|
160
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
161
|
+
|
|
162
|
+
def get_cycle_time(self) -> Optional[float]:
|
|
163
|
+
"""Get cycle time in hours.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Cycle time in hours if PR is merged, None otherwise
|
|
167
|
+
"""
|
|
168
|
+
if self.is_merged and self.merged_at and self.created_at:
|
|
169
|
+
delta = self.merged_at - self.created_at
|
|
170
|
+
return delta.total_seconds() / 3600
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class DeveloperMetrics:
|
|
176
|
+
"""Developer-level metrics."""
|
|
177
|
+
|
|
178
|
+
developer: DeveloperIdentity
|
|
179
|
+
|
|
180
|
+
# Activity metrics
|
|
181
|
+
total_commits: int = 0
|
|
182
|
+
total_prs: int = 0
|
|
183
|
+
lines_added: int = 0
|
|
184
|
+
lines_deleted: int = 0
|
|
185
|
+
files_changed: int = 0
|
|
186
|
+
|
|
187
|
+
# Time-based metrics
|
|
188
|
+
active_days: int = 0
|
|
189
|
+
first_commit: Optional[datetime] = None
|
|
190
|
+
last_commit: Optional[datetime] = None
|
|
191
|
+
|
|
192
|
+
# Quality metrics
|
|
193
|
+
ticket_coverage_pct: float = 0.0
|
|
194
|
+
review_participation_count: int = 0
|
|
195
|
+
|
|
196
|
+
# Story points
|
|
197
|
+
total_story_points: float = 0.0
|
|
198
|
+
|
|
199
|
+
# Work patterns
|
|
200
|
+
work_style: Optional[WorkStyle] = None
|
|
201
|
+
primary_project: Optional[str] = None
|
|
202
|
+
projects: Dict[str, float] = field(default_factory=dict) # project -> percentage
|
|
203
|
+
|
|
204
|
+
# Commit categorization
|
|
205
|
+
commit_types: Dict[CommitType, int] = field(default_factory=dict)
|
|
206
|
+
|
|
207
|
+
# Additional metrics
|
|
208
|
+
velocity: float = 0.0 # commits per week
|
|
209
|
+
impact_score: float = 0.0
|
|
210
|
+
collaboration_score: float = 0.0
|
|
211
|
+
|
|
212
|
+
def get_productivity_score(self) -> float:
|
|
213
|
+
"""Calculate overall productivity score.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Productivity score (0-100)
|
|
217
|
+
"""
|
|
218
|
+
# Simple scoring algorithm
|
|
219
|
+
score = 0.0
|
|
220
|
+
|
|
221
|
+
# Activity component (40%)
|
|
222
|
+
activity_score = min(40, (self.total_commits / 10) * 4)
|
|
223
|
+
score += activity_score
|
|
224
|
+
|
|
225
|
+
# Quality component (30%)
|
|
226
|
+
quality_score = self.ticket_coverage_pct * 0.3
|
|
227
|
+
score += quality_score
|
|
228
|
+
|
|
229
|
+
# Collaboration component (20%)
|
|
230
|
+
collab_score = min(20, (self.review_participation_count / 5) * 20)
|
|
231
|
+
score += collab_score
|
|
232
|
+
|
|
233
|
+
# Consistency component (10%)
|
|
234
|
+
if self.work_style == WorkStyle.CONSISTENT:
|
|
235
|
+
score += 10
|
|
236
|
+
elif self.work_style == WorkStyle.GROWING:
|
|
237
|
+
score += 8
|
|
238
|
+
elif self.work_style == WorkStyle.BURST:
|
|
239
|
+
score += 5
|
|
240
|
+
|
|
241
|
+
return min(100, score)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@dataclass
|
|
245
|
+
class ProjectMetrics:
|
|
246
|
+
"""Project-level metrics."""
|
|
247
|
+
|
|
248
|
+
project_key: str
|
|
249
|
+
project_name: Optional[str] = None
|
|
250
|
+
|
|
251
|
+
# Activity metrics
|
|
252
|
+
total_commits: int = 0
|
|
253
|
+
total_prs: int = 0
|
|
254
|
+
active_developers: int = 0
|
|
255
|
+
lines_added: int = 0
|
|
256
|
+
lines_deleted: int = 0
|
|
257
|
+
|
|
258
|
+
# Time-based metrics
|
|
259
|
+
start_date: Optional[date] = None
|
|
260
|
+
end_date: Optional[date] = None
|
|
261
|
+
active_days: int = 0
|
|
262
|
+
|
|
263
|
+
# Quality metrics
|
|
264
|
+
ticket_coverage_pct: float = 0.0
|
|
265
|
+
pr_merge_rate: float = 0.0
|
|
266
|
+
avg_pr_cycle_time_hours: float = 0.0
|
|
267
|
+
|
|
268
|
+
# Story points
|
|
269
|
+
total_story_points: float = 0.0
|
|
270
|
+
velocity: float = 0.0 # story points per week
|
|
271
|
+
|
|
272
|
+
# Commit categorization
|
|
273
|
+
commit_types: Dict[CommitType, int] = field(default_factory=dict)
|
|
274
|
+
|
|
275
|
+
# Developer breakdown
|
|
276
|
+
developer_contributions: Dict[str, float] = field(default_factory=dict) # developer -> percentage
|
|
277
|
+
|
|
278
|
+
# Health indicators
|
|
279
|
+
health_score: float = 0.0
|
|
280
|
+
risk_level: str = "low" # low, medium, high
|
|
281
|
+
|
|
282
|
+
def calculate_health_score(self) -> float:
|
|
283
|
+
"""Calculate project health score.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Health score (0-100)
|
|
287
|
+
"""
|
|
288
|
+
score = 0.0
|
|
289
|
+
|
|
290
|
+
# Activity health (30%)
|
|
291
|
+
if self.active_developers > 0:
|
|
292
|
+
activity_score = min(30, (self.total_commits / self.active_developers / 10) * 3)
|
|
293
|
+
score += activity_score
|
|
294
|
+
|
|
295
|
+
# Quality health (40%)
|
|
296
|
+
quality_score = self.ticket_coverage_pct * 0.4
|
|
297
|
+
score += quality_score
|
|
298
|
+
|
|
299
|
+
# Velocity health (30%)
|
|
300
|
+
if self.velocity > 0:
|
|
301
|
+
velocity_score = min(30, (self.velocity / 10) * 30)
|
|
302
|
+
score += velocity_score
|
|
303
|
+
|
|
304
|
+
self.health_score = min(100, score)
|
|
305
|
+
|
|
306
|
+
# Determine risk level
|
|
307
|
+
if score >= 80:
|
|
308
|
+
self.risk_level = "low"
|
|
309
|
+
elif score >= 50:
|
|
310
|
+
self.risk_level = "medium"
|
|
311
|
+
else:
|
|
312
|
+
self.risk_level = "high"
|
|
313
|
+
|
|
314
|
+
return self.health_score
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass
|
|
318
|
+
class WeeklyMetrics:
|
|
319
|
+
"""Weekly aggregated metrics."""
|
|
320
|
+
|
|
321
|
+
week_start: date
|
|
322
|
+
week_end: date
|
|
323
|
+
week_number: int
|
|
324
|
+
year: int
|
|
325
|
+
|
|
326
|
+
# Activity metrics
|
|
327
|
+
commits: int = 0
|
|
328
|
+
pull_requests: int = 0
|
|
329
|
+
active_developers: int = 0
|
|
330
|
+
lines_added: int = 0
|
|
331
|
+
lines_deleted: int = 0
|
|
332
|
+
|
|
333
|
+
# Quality metrics
|
|
334
|
+
ticket_coverage_pct: float = 0.0
|
|
335
|
+
bug_fix_ratio: float = 0.0
|
|
336
|
+
|
|
337
|
+
# Story points
|
|
338
|
+
story_points_completed: float = 0.0
|
|
339
|
+
|
|
340
|
+
# DORA metrics
|
|
341
|
+
deployment_frequency: int = 0
|
|
342
|
+
lead_time_hours: Optional[float] = None
|
|
343
|
+
mttr_hours: Optional[float] = None # Mean time to recovery
|
|
344
|
+
change_failure_rate: float = 0.0
|
|
345
|
+
|
|
346
|
+
# Trends
|
|
347
|
+
commit_trend: float = 0.0 # % change from previous week
|
|
348
|
+
developer_trend: float = 0.0 # % change from previous week
|
|
349
|
+
|
|
350
|
+
# Project breakdown
|
|
351
|
+
project_activity: Dict[str, int] = field(default_factory=dict) # project -> commits
|
|
352
|
+
|
|
353
|
+
# Developer breakdown
|
|
354
|
+
developer_activity: Dict[str, int] = field(default_factory=dict) # developer -> commits
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass
|
|
358
|
+
class TicketMetrics:
|
|
359
|
+
"""Ticket/issue tracking metrics."""
|
|
360
|
+
|
|
361
|
+
platform: str # jira, github, gitlab, etc.
|
|
362
|
+
total_tickets: int = 0
|
|
363
|
+
unique_tickets: int = 0
|
|
364
|
+
|
|
365
|
+
# Coverage
|
|
366
|
+
commits_with_tickets: int = 0
|
|
367
|
+
commits_without_tickets: int = 0
|
|
368
|
+
coverage_percentage: float = 0.0
|
|
369
|
+
|
|
370
|
+
# Ticket types
|
|
371
|
+
ticket_types: Dict[str, int] = field(default_factory=dict)
|
|
372
|
+
|
|
373
|
+
# Per-developer coverage
|
|
374
|
+
developer_coverage: Dict[str, float] = field(default_factory=dict)
|
|
375
|
+
|
|
376
|
+
# Per-project coverage
|
|
377
|
+
project_coverage: Dict[str, float] = field(default_factory=dict)
|
|
378
|
+
|
|
379
|
+
# Untracked work analysis
|
|
380
|
+
untracked_categories: Dict[str, int] = field(default_factory=dict)
|
|
381
|
+
untracked_developers: Dict[str, int] = field(default_factory=dict)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@dataclass
|
|
385
|
+
class DORAMetrics:
|
|
386
|
+
"""DORA (DevOps Research and Assessment) metrics."""
|
|
387
|
+
|
|
388
|
+
# Elite performance thresholds
|
|
389
|
+
deployment_frequency: float = 0.0 # deployments per day
|
|
390
|
+
lead_time_for_changes: float = 0.0 # hours
|
|
391
|
+
time_to_restore_service: float = 0.0 # hours
|
|
392
|
+
change_failure_rate: float = 0.0 # percentage
|
|
393
|
+
|
|
394
|
+
# Performance level
|
|
395
|
+
performance_level: str = "low" # low, medium, high, elite
|
|
396
|
+
|
|
397
|
+
# Breakdown by period
|
|
398
|
+
weekly_metrics: List[WeeklyMetrics] = field(default_factory=list)
|
|
399
|
+
|
|
400
|
+
def calculate_performance_level(self) -> str:
|
|
401
|
+
"""Calculate DORA performance level.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Performance level (low, medium, high, elite)
|
|
405
|
+
"""
|
|
406
|
+
score = 0
|
|
407
|
+
|
|
408
|
+
# Deployment frequency scoring
|
|
409
|
+
if self.deployment_frequency >= 1: # Daily
|
|
410
|
+
score += 4
|
|
411
|
+
elif self.deployment_frequency >= 0.14: # Weekly
|
|
412
|
+
score += 3
|
|
413
|
+
elif self.deployment_frequency >= 0.03: # Monthly
|
|
414
|
+
score += 2
|
|
415
|
+
else:
|
|
416
|
+
score += 1
|
|
417
|
+
|
|
418
|
+
# Lead time scoring
|
|
419
|
+
if self.lead_time_for_changes <= 24: # Less than a day
|
|
420
|
+
score += 4
|
|
421
|
+
elif self.lead_time_for_changes <= 168: # Less than a week
|
|
422
|
+
score += 3
|
|
423
|
+
elif self.lead_time_for_changes <= 720: # Less than a month
|
|
424
|
+
score += 2
|
|
425
|
+
else:
|
|
426
|
+
score += 1
|
|
427
|
+
|
|
428
|
+
# MTTR scoring
|
|
429
|
+
if self.time_to_restore_service <= 1: # Less than an hour
|
|
430
|
+
score += 4
|
|
431
|
+
elif self.time_to_restore_service <= 24: # Less than a day
|
|
432
|
+
score += 3
|
|
433
|
+
elif self.time_to_restore_service <= 168: # Less than a week
|
|
434
|
+
score += 2
|
|
435
|
+
else:
|
|
436
|
+
score += 1
|
|
437
|
+
|
|
438
|
+
# Change failure rate scoring
|
|
439
|
+
if self.change_failure_rate <= 5: # 0-5%
|
|
440
|
+
score += 4
|
|
441
|
+
elif self.change_failure_rate <= 10: # 6-10%
|
|
442
|
+
score += 3
|
|
443
|
+
elif self.change_failure_rate <= 15: # 11-15%
|
|
444
|
+
score += 2
|
|
445
|
+
else:
|
|
446
|
+
score += 1
|
|
447
|
+
|
|
448
|
+
# Calculate performance level
|
|
449
|
+
avg_score = score / 4
|
|
450
|
+
if avg_score >= 3.5:
|
|
451
|
+
self.performance_level = "elite"
|
|
452
|
+
elif avg_score >= 2.5:
|
|
453
|
+
self.performance_level = "high"
|
|
454
|
+
elif avg_score >= 1.5:
|
|
455
|
+
self.performance_level = "medium"
|
|
456
|
+
else:
|
|
457
|
+
self.performance_level = "low"
|
|
458
|
+
|
|
459
|
+
return self.performance_level
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@dataclass
|
|
463
|
+
class ReportSummary:
|
|
464
|
+
"""Summary data for comprehensive reports."""
|
|
465
|
+
|
|
466
|
+
# Period information
|
|
467
|
+
start_date: date
|
|
468
|
+
end_date: date
|
|
469
|
+
analysis_weeks: int
|
|
470
|
+
|
|
471
|
+
# High-level metrics
|
|
472
|
+
total_commits: int = 0
|
|
473
|
+
total_pull_requests: int = 0
|
|
474
|
+
total_developers: int = 0
|
|
475
|
+
total_projects: int = 0
|
|
476
|
+
total_lines_changed: int = 0
|
|
477
|
+
|
|
478
|
+
# Quality metrics
|
|
479
|
+
overall_ticket_coverage: float = 0.0
|
|
480
|
+
overall_pr_merge_rate: float = 0.0
|
|
481
|
+
|
|
482
|
+
# Story points
|
|
483
|
+
total_story_points: float = 0.0
|
|
484
|
+
average_velocity: float = 0.0
|
|
485
|
+
|
|
486
|
+
# Top performers
|
|
487
|
+
top_contributors: List[DeveloperIdentity] = field(default_factory=list)
|
|
488
|
+
most_active_projects: List[str] = field(default_factory=list)
|
|
489
|
+
|
|
490
|
+
# Health indicators
|
|
491
|
+
overall_health_score: float = 0.0
|
|
492
|
+
risk_projects: List[str] = field(default_factory=list)
|
|
493
|
+
|
|
494
|
+
# DORA metrics
|
|
495
|
+
dora_metrics: Optional[DORAMetrics] = None
|
|
496
|
+
|
|
497
|
+
# Trends
|
|
498
|
+
commit_trend: str = "stable" # declining, stable, growing
|
|
499
|
+
developer_trend: str = "stable"
|
|
500
|
+
velocity_trend: str = "stable"
|
|
501
|
+
|
|
502
|
+
# Key findings
|
|
503
|
+
key_findings: List[str] = field(default_factory=list)
|
|
504
|
+
recommendations: List[str] = field(default_factory=list)
|