gitflow-analytics 1.0.3__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 (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 +4108 -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 +904 -0
  13. gitflow_analytics/config/profiles.py +264 -0
  14. gitflow_analytics/config/repository.py +124 -0
  15. gitflow_analytics/config/schema.py +441 -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 +1193 -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.6.dist-info/METADATA +1015 -0
  110. gitflow_analytics-1.3.6.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.6.dist-info}/WHEEL +0 -0
  114. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/entry_points.txt +0 -0
  115. {gitflow_analytics-1.0.3.dist-info → gitflow_analytics-1.3.6.dist-info}/licenses/LICENSE +0 -0
  116. {gitflow_analytics-1.0.3.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)