greenmining 1.2.3__py3-none-any.whl → 1.2.5__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.
greenmining/__init__.py CHANGED
@@ -8,7 +8,7 @@ from greenmining.gsf_patterns import (
8
8
  is_green_aware,
9
9
  )
10
10
 
11
- __version__ = "1.2.3"
11
+ __version__ = "1.2.5"
12
12
 
13
13
 
14
14
  def fetch_repositories(
@@ -73,6 +73,8 @@ def analyze_repositories(
73
73
  since_date: str = None,
74
74
  to_date: str = None,
75
75
  cleanup_after: bool = True,
76
+ skip_merges: bool = True,
77
+ commit_order: str = "newest_first",
76
78
  ):
77
79
  # Analyze multiple repositories from URLs.
78
80
  # Args:
@@ -88,6 +90,9 @@ def analyze_repositories(
88
90
  # github_token: GitHub token for private HTTPS repositories
89
91
  # since_date: Analyze commits from this date (YYYY-MM-DD string)
90
92
  # to_date: Analyze commits up to this date (YYYY-MM-DD string)
93
+ # cleanup_after: Remove cloned repos after analysis (default True)
94
+ # skip_merges: Skip merge commits (default True)
95
+ # commit_order: "newest_first" (default) or "oldest_first"
91
96
  from greenmining.services.local_repo_analyzer import LocalRepoAnalyzer
92
97
 
93
98
  kwargs = {}
@@ -109,6 +114,8 @@ def analyze_repositories(
109
114
  ssh_key_path=ssh_key_path,
110
115
  github_token=github_token,
111
116
  cleanup_after=cleanup_after,
117
+ skip_merges=skip_merges,
118
+ commit_order=commit_order,
112
119
  **kwargs,
113
120
  )
114
121
 
@@ -1,9 +1,9 @@
1
1
  # Analyzers for GreenMining framework.
2
2
 
3
3
  from .code_diff_analyzer import CodeDiffAnalyzer
4
+ from .metrics_power_correlator import CorrelationResult, MetricsPowerCorrelator
4
5
  from .statistical_analyzer import StatisticalAnalyzer
5
6
  from .temporal_analyzer import TemporalAnalyzer
6
- from .metrics_power_correlator import MetricsPowerCorrelator, CorrelationResult
7
7
 
8
8
  __all__ = [
9
9
  "CodeDiffAnalyzer",
@@ -1,7 +1,7 @@
1
1
  # Code diff analyzer for detecting green software patterns in code changes.
2
2
 
3
3
  import re
4
- from typing import Any, Dict, List
4
+ from typing import Any
5
5
 
6
6
  from pydriller import Commit, ModifiedFile
7
7
 
@@ -207,7 +207,7 @@ class CodeDiffAnalyzer:
207
207
  },
208
208
  }
209
209
 
210
- def analyze_commit_diff(self, commit: Commit) -> Dict[str, Any]:
210
+ def analyze_commit_diff(self, commit: Commit) -> dict[str, Any]:
211
211
  # Analyze code changes in a commit to detect green patterns.
212
212
  patterns_detected = []
213
213
  evidence = {}
@@ -244,12 +244,12 @@ class CodeDiffAnalyzer:
244
244
  "metrics": metrics,
245
245
  }
246
246
 
247
- def _detect_patterns_in_line(self, code_line: str) -> List[str]:
247
+ def _detect_patterns_in_line(self, code_line: str) -> list[str]:
248
248
  # Detect patterns in a single line of code.
249
249
  detected = []
250
250
 
251
251
  for pattern_name, signatures in self.PATTERN_SIGNATURES.items():
252
- for signature_type, patterns in signatures.items():
252
+ for _signature_type, patterns in signatures.items():
253
253
  for pattern_regex in patterns:
254
254
  if re.search(pattern_regex, code_line, re.IGNORECASE):
255
255
  detected.append(pattern_name)
@@ -257,7 +257,7 @@ class CodeDiffAnalyzer:
257
257
 
258
258
  return detected
259
259
 
260
- def _calculate_metrics(self, commit: Commit) -> Dict[str, int]:
260
+ def _calculate_metrics(self, commit: Commit) -> dict[str, int]:
261
261
  # Calculate code change metrics.
262
262
  lines_added = sum(f.added_lines for f in commit.modified_files)
263
263
  lines_removed = sum(f.deleted_lines for f in commit.modified_files)
@@ -276,7 +276,7 @@ class CodeDiffAnalyzer:
276
276
  }
277
277
 
278
278
  def _calculate_diff_confidence(
279
- self, patterns: List[str], evidence: Dict[str, List[str]], metrics: Dict[str, int]
279
+ self, patterns: list[str], evidence: dict[str, list[str]], metrics: dict[str, int]
280
280
  ) -> str:
281
281
  # Calculate confidence level for diff-based detection.
282
282
  if not patterns:
@@ -3,8 +3,8 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from dataclasses import dataclass, field
7
- from typing import Any, Dict, List, Optional
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
8
 
9
9
  import numpy as np
10
10
  from scipy import stats
@@ -22,7 +22,7 @@ class CorrelationResult:
22
22
  significant: bool = False
23
23
  strength: str = "none"
24
24
 
25
- def to_dict(self) -> Dict[str, Any]:
25
+ def to_dict(self) -> dict[str, Any]:
26
26
  return {
27
27
  "metric_name": self.metric_name,
28
28
  "pearson_r": round(self.pearson_r, 4),
@@ -44,17 +44,17 @@ class MetricsPowerCorrelator:
44
44
  # Args:
45
45
  # significance_level: P-value threshold for significance
46
46
  self.significance_level = significance_level
47
- self._metrics_data: Dict[str, List[float]] = {}
48
- self._power_data: List[float] = []
47
+ self._metrics_data: dict[str, list[float]] = {}
48
+ self._power_data: list[float] = []
49
49
  self._fitted = False
50
- self._results: Dict[str, CorrelationResult] = {}
51
- self._feature_importance: Dict[str, float] = {}
50
+ self._results: dict[str, CorrelationResult] = {}
51
+ self._feature_importance: dict[str, float] = {}
52
52
 
53
53
  def fit(
54
54
  self,
55
- metrics: List[str],
56
- metrics_values: Dict[str, List[float]],
57
- power_measurements: List[float],
55
+ metrics: list[str],
56
+ metrics_values: dict[str, list[float]],
57
+ power_measurements: list[float],
58
58
  ) -> None:
59
59
  # Fit the correlator with metrics and power data.
60
60
  # Args:
@@ -86,7 +86,7 @@ class MetricsPowerCorrelator:
86
86
  self._fitted = True
87
87
 
88
88
  def _compute_correlation(
89
- self, metric_name: str, metric_values: List[float], power_values: List[float]
89
+ self, metric_name: str, metric_values: list[float], power_values: list[float]
90
90
  ) -> CorrelationResult:
91
91
  # Compute Pearson and Spearman correlations for a single metric.
92
92
  x = np.array(metric_values, dtype=float)
@@ -127,29 +127,29 @@ class MetricsPowerCorrelator:
127
127
  )
128
128
 
129
129
  @property
130
- def pearson(self) -> Dict[str, float]:
130
+ def pearson(self) -> dict[str, float]:
131
131
  # Get Pearson correlations for all metrics.
132
132
  return {name: r.pearson_r for name, r in self._results.items()}
133
133
 
134
134
  @property
135
- def spearman(self) -> Dict[str, float]:
135
+ def spearman(self) -> dict[str, float]:
136
136
  # Get Spearman correlations for all metrics.
137
137
  return {name: r.spearman_r for name, r in self._results.items()}
138
138
 
139
139
  @property
140
- def feature_importance(self) -> Dict[str, float]:
140
+ def feature_importance(self) -> dict[str, float]:
141
141
  # Get normalized feature importance scores.
142
142
  return self._feature_importance
143
143
 
144
- def get_results(self) -> Dict[str, CorrelationResult]:
144
+ def get_results(self) -> dict[str, CorrelationResult]:
145
145
  # Get all correlation results.
146
146
  return self._results
147
147
 
148
- def get_significant_correlations(self) -> Dict[str, CorrelationResult]:
148
+ def get_significant_correlations(self) -> dict[str, CorrelationResult]:
149
149
  # Get only statistically significant correlations.
150
150
  return {name: r for name, r in self._results.items() if r.significant}
151
151
 
152
- def summary(self) -> Dict[str, Any]:
152
+ def summary(self) -> dict[str, Any]:
153
153
  # Generate summary of correlation analysis.
154
154
  return {
155
155
  "total_metrics": len(self._results),
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Dict, List
5
+ from typing import Any
6
6
 
7
7
  import numpy as np
8
8
  import pandas as pd
@@ -12,7 +12,7 @@ from scipy import stats
12
12
  class StatisticalAnalyzer:
13
13
  # Advanced statistical analyses for green software patterns.
14
14
 
15
- def analyze_pattern_correlations(self, commit_data: pd.DataFrame) -> Dict[str, Any]:
15
+ def analyze_pattern_correlations(self, commit_data: pd.DataFrame) -> dict[str, Any]:
16
16
  # Analyze correlations between patterns.
17
17
  # Create pattern co-occurrence matrix
18
18
  pattern_columns = [col for col in commit_data.columns if col.startswith("pattern_")]
@@ -47,7 +47,7 @@ class StatisticalAnalyzer:
47
47
  "interpretation": self._interpret_correlations(significant_pairs),
48
48
  }
49
49
 
50
- def temporal_trend_analysis(self, commits_df: pd.DataFrame) -> Dict[str, Any]:
50
+ def temporal_trend_analysis(self, commits_df: pd.DataFrame) -> dict[str, Any]:
51
51
  # Analyze temporal trends in green awareness.
52
52
  # Prepare time series data
53
53
  commits_df["date"] = pd.to_datetime(commits_df["date"], utc=True, errors="coerce")
@@ -101,7 +101,7 @@ class StatisticalAnalyzer:
101
101
  "monthly_data": monthly.to_dict(),
102
102
  }
103
103
 
104
- def effect_size_analysis(self, group1: List[float], group2: List[float]) -> Dict[str, Any]:
104
+ def effect_size_analysis(self, group1: list[float], group2: list[float]) -> dict[str, Any]:
105
105
  # Calculate effect size between two groups.
106
106
  # Cohen's d (effect size)
107
107
  mean1, mean2 = np.mean(group1), np.mean(group2)
@@ -135,7 +135,7 @@ class StatisticalAnalyzer:
135
135
  "significant": bool(p_value < 0.05),
136
136
  }
137
137
 
138
- def _interpret_correlations(self, significant_pairs: List[Dict[str, Any]]) -> str:
138
+ def _interpret_correlations(self, significant_pairs: list[dict[str, Any]]) -> str:
139
139
  # Generate interpretation of correlation results.
140
140
  if not significant_pairs:
141
141
  return "No significant correlations found between patterns."
@@ -2,11 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from datetime import datetime, timedelta
6
- from typing import Dict, List, Optional, Tuple
7
- from dataclasses import dataclass
8
- from collections import defaultdict
9
5
  import statistics
6
+ from collections import defaultdict
7
+ from dataclasses import dataclass
8
+ from datetime import datetime, timedelta
10
9
 
11
10
 
12
11
  @dataclass
@@ -20,7 +19,7 @@ class TemporalMetrics:
20
19
  green_commit_count: int
21
20
  green_awareness_rate: float
22
21
  unique_patterns: int
23
- dominant_pattern: Optional[str]
22
+ dominant_pattern: str | None
24
23
  velocity: float # commits per day
25
24
 
26
25
 
@@ -44,8 +43,8 @@ class TemporalAnalyzer:
44
43
  self.granularity = granularity
45
44
 
46
45
  def group_commits_by_period(
47
- self, commits: List[Dict], date_field: str = "date"
48
- ) -> Dict[str, List[Dict]]:
46
+ self, commits: list[dict], date_field: str = "date"
47
+ ) -> dict[str, list[dict]]:
49
48
  # Group commits into time periods.
50
49
  periods = defaultdict(list)
51
50
 
@@ -86,7 +85,7 @@ class TemporalAnalyzer:
86
85
  else:
87
86
  return date.strftime("%Y-%m")
88
87
 
89
- def _parse_period_key(self, period_key: str) -> Tuple[datetime, datetime]:
88
+ def _parse_period_key(self, period_key: str) -> tuple[datetime, datetime]:
90
89
  # Parse period key back to start and end dates.
91
90
  if "W" in period_key:
92
91
  # Week format: 2024-W15
@@ -138,7 +137,7 @@ class TemporalAnalyzer:
138
137
  return start, end
139
138
 
140
139
  def calculate_period_metrics(
141
- self, period_key: str, commits: List[Dict], analysis_results: List[Dict]
140
+ self, period_key: str, commits: list[dict], analysis_results: list[dict]
142
141
  ) -> TemporalMetrics:
143
142
  # Calculate metrics for a time period.
144
143
  start_date, end_date = self._parse_period_key(period_key)
@@ -185,7 +184,7 @@ class TemporalAnalyzer:
185
184
  velocity=round(velocity, 2),
186
185
  )
187
186
 
188
- def analyze_trends(self, commits: List[Dict], analysis_results: List[Dict]) -> Dict:
187
+ def analyze_trends(self, commits: list[dict], analysis_results: list[dict]) -> dict:
189
188
  # Comprehensive temporal trend analysis.
190
189
  # Group by periods
191
190
  grouped = self.group_commits_by_period(commits)
@@ -227,7 +226,7 @@ class TemporalAnalyzer:
227
226
  },
228
227
  }
229
228
 
230
- def _calculate_trend(self, periods: List[TemporalMetrics]) -> Optional[TrendAnalysis]:
229
+ def _calculate_trend(self, periods: list[TemporalMetrics]) -> TrendAnalysis | None:
231
230
  # Calculate linear trend using least squares regression.
232
231
  if len(periods) < 2:
233
232
  return None
@@ -275,7 +274,7 @@ class TemporalAnalyzer:
275
274
  change_percentage=round(change, 2),
276
275
  )
277
276
 
278
- def _calculate_adoption_curve(self, periods: List[TemporalMetrics]) -> List[Tuple[str, float]]:
277
+ def _calculate_adoption_curve(self, periods: list[TemporalMetrics]) -> list[tuple[str, float]]:
279
278
  # Calculate cumulative adoption over time.
280
279
  cumulative_green = 0
281
280
  cumulative_total = 0
@@ -291,7 +290,7 @@ class TemporalAnalyzer:
291
290
 
292
291
  return curve
293
292
 
294
- def _calculate_velocity_trend(self, periods: List[TemporalMetrics]) -> Dict:
293
+ def _calculate_velocity_trend(self, periods: list[TemporalMetrics]) -> dict:
295
294
  # Analyze velocity changes over time.
296
295
  if not periods:
297
296
  return {}
@@ -307,8 +306,8 @@ class TemporalAnalyzer:
307
306
  }
308
307
 
309
308
  def _analyze_pattern_evolution(
310
- self, periods: List[TemporalMetrics], analysis_results: List[Dict]
311
- ) -> Dict:
309
+ self, periods: list[TemporalMetrics], analysis_results: list[dict]
310
+ ) -> dict:
312
311
  # Track when different patterns emerged and dominated.
313
312
  pattern_timeline = defaultdict(lambda: {"first_seen": None, "occurrences_by_period": {}})
314
313
 
@@ -349,7 +348,7 @@ class TemporalAnalyzer:
349
348
  for pattern, data in pattern_timeline.items()
350
349
  }
351
350
 
352
- def _metrics_to_dict(self, metrics: TemporalMetrics) -> Dict:
351
+ def _metrics_to_dict(self, metrics: TemporalMetrics) -> dict:
353
352
  # Convert TemporalMetrics to dictionary.
354
353
  return {
355
354
  "period": metrics.period,
@@ -363,7 +362,7 @@ class TemporalAnalyzer:
363
362
  "velocity": metrics.velocity,
364
363
  }
365
364
 
366
- def _trend_to_dict(self, trend: Optional[TrendAnalysis]) -> Dict:
365
+ def _trend_to_dict(self, trend: TrendAnalysis | None) -> dict:
367
366
  # Convert TrendAnalysis to dictionary.
368
367
  if not trend:
369
368
  return {}
@@ -41,9 +41,7 @@ class RepositoryController:
41
41
  f" Created: {created_after or 'any'} to {created_before or 'any'}", "cyan"
42
42
  )
43
43
  if pushed_after or pushed_before:
44
- colored_print(
45
- f" Pushed: {pushed_after or 'any'} to {pushed_before or 'any'}", "cyan"
46
- )
44
+ colored_print(f" Pushed: {pushed_after or 'any'} to {pushed_before or 'any'}", "cyan")
47
45
 
48
46
  try:
49
47
  repositories = self.graphql_fetcher.search_repositories(
@@ -1,10 +1,10 @@
1
1
  # Energy measurement module for GreenMining.
2
2
 
3
- from .base import EnergyMeter, EnergyMetrics, EnergyBackend, CommitEnergyProfile, get_energy_meter
4
- from .rapl import RAPLEnergyMeter
3
+ from .base import CommitEnergyProfile, EnergyBackend, EnergyMeter, EnergyMetrics, get_energy_meter
4
+ from .carbon_reporter import CarbonReport, CarbonReporter
5
5
  from .codecarbon_meter import CodeCarbonMeter
6
6
  from .cpu_meter import CPUEnergyMeter
7
- from .carbon_reporter import CarbonReporter, CarbonReport
7
+ from .rapl import RAPLEnergyMeter
8
8
 
9
9
  __all__ = [
10
10
  "EnergyMeter",
@@ -3,11 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
- from dataclasses import dataclass, field
6
+ from dataclasses import dataclass
7
7
  from datetime import datetime
8
8
  from enum import Enum
9
- from typing import Any, Dict, List, Optional, Callable
10
- import time
9
+ from typing import Any, Callable
11
10
 
12
11
 
13
12
  class EnergyBackend(Enum):
@@ -31,16 +30,16 @@ class EnergyMetrics:
31
30
  # Component-specific energy (if available)
32
31
  cpu_energy_joules: float = 0.0 # CPU-specific energy
33
32
  dram_energy_joules: float = 0.0 # Memory energy
34
- gpu_energy_joules: Optional[float] = None # GPU energy if available
33
+ gpu_energy_joules: float | None = None # GPU energy if available
35
34
 
36
35
  # Carbon footprint (if carbon tracking enabled)
37
- carbon_grams: Optional[float] = None # CO2 equivalent in grams
38
- carbon_intensity: Optional[float] = None # gCO2/kWh of grid
36
+ carbon_grams: float | None = None # CO2 equivalent in grams
37
+ carbon_intensity: float | None = None # gCO2/kWh of grid
39
38
 
40
39
  # Metadata
41
40
  backend: str = ""
42
- start_time: Optional[datetime] = None
43
- end_time: Optional[datetime] = None
41
+ start_time: datetime | None = None
42
+ end_time: datetime | None = None
44
43
 
45
44
  @property
46
45
  def energy_joules(self) -> float:
@@ -50,7 +49,7 @@ class EnergyMetrics:
50
49
  def average_power_watts(self) -> float:
51
50
  return self.watts_avg
52
51
 
53
- def to_dict(self) -> Dict[str, Any]:
52
+ def to_dict(self) -> dict[str, Any]:
54
53
  # Convert to dictionary.
55
54
  return {
56
55
  "joules": self.joules,
@@ -73,13 +72,13 @@ class CommitEnergyProfile:
73
72
  # Energy profile for a specific commit.
74
73
 
75
74
  commit_hash: str
76
- energy_before: Optional[EnergyMetrics] = None # Parent commit energy
77
- energy_after: Optional[EnergyMetrics] = None # This commit energy
75
+ energy_before: EnergyMetrics | None = None # Parent commit energy
76
+ energy_after: EnergyMetrics | None = None # This commit energy
78
77
  energy_delta: float = 0.0 # Change in joules
79
78
  energy_regression: bool = False # True if energy increased
80
79
  regression_percentage: float = 0.0 # % change
81
80
 
82
- def to_dict(self) -> Dict[str, Any]:
81
+ def to_dict(self) -> dict[str, Any]:
83
82
  # Convert to dictionary.
84
83
  return {
85
84
  "commit_hash": self.commit_hash,
@@ -98,8 +97,8 @@ class EnergyMeter(ABC):
98
97
  # Initialize the energy meter.
99
98
  self.backend = backend
100
99
  self._is_measuring = False
101
- self._start_time: Optional[float] = None
102
- self._measurements: List[float] = []
100
+ self._start_time: float | None = None
101
+ self._measurements: list[float] = []
103
102
 
104
103
  @abstractmethod
105
104
  def is_available(self) -> bool:
@@ -125,7 +124,7 @@ class EnergyMeter(ABC):
125
124
  metrics = self.stop()
126
125
  return result, metrics
127
126
 
128
- def measure_command(self, command: str, timeout: Optional[int] = None) -> EnergyMetrics:
127
+ def measure_command(self, command: str, timeout: int | None = None) -> EnergyMetrics:
129
128
  # Measure energy consumption of a shell command.
130
129
  import subprocess
131
130
 
@@ -156,9 +155,9 @@ class EnergyMeter(ABC):
156
155
  def get_energy_meter(backend: str = "rapl") -> EnergyMeter:
157
156
  # Factory function to get an energy meter instance.
158
157
  # Supported backends: rapl, codecarbon, cpu_meter, auto
159
- from .rapl import RAPLEnergyMeter
160
158
  from .codecarbon_meter import CodeCarbonMeter
161
159
  from .cpu_meter import CPUEnergyMeter
160
+ from .rapl import RAPLEnergyMeter
162
161
 
163
162
  backend_lower = backend.lower()
164
163
 
@@ -4,7 +4,7 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  from dataclasses import dataclass, field
7
- from typing import Any, Dict, List, Optional
7
+ from typing import Any
8
8
 
9
9
  from .base import EnergyMetrics
10
10
 
@@ -88,9 +88,9 @@ class CarbonReport:
88
88
  tree_months: float = 0.0 # Equivalent tree-months to offset
89
89
  smartphone_charges: float = 0.0 # Equivalent smartphone charges
90
90
  km_driven: float = 0.0 # Equivalent km driven in average car
91
- analysis_results: List[Dict[str, Any]] = field(default_factory=list)
91
+ analysis_results: list[dict[str, Any]] = field(default_factory=list)
92
92
 
93
- def to_dict(self) -> Dict[str, Any]:
93
+ def to_dict(self) -> dict[str, Any]:
94
94
  return {
95
95
  "total_energy_kwh": round(self.total_energy_kwh, 6),
96
96
  "total_emissions_kg": round(self.total_emissions_kg, 6),
@@ -143,8 +143,8 @@ class CarbonReporter:
143
143
  def __init__(
144
144
  self,
145
145
  country_iso: str = "USA",
146
- cloud_provider: Optional[str] = None,
147
- region: Optional[str] = None,
146
+ cloud_provider: str | None = None,
147
+ region: str | None = None,
148
148
  ):
149
149
  # Initialize carbon reporter.
150
150
  # Args:
@@ -169,9 +169,9 @@ class CarbonReporter:
169
169
 
170
170
  def generate_report(
171
171
  self,
172
- energy_metrics: Optional[EnergyMetrics] = None,
173
- analysis_results: Optional[List[Dict[str, Any]]] = None,
174
- total_joules: Optional[float] = None,
172
+ energy_metrics: EnergyMetrics | None = None,
173
+ analysis_results: list[dict[str, Any]] | None = None,
174
+ total_joules: float | None = None,
175
175
  ) -> CarbonReport:
176
176
  # Generate a carbon footprint report.
177
177
  # Args:
@@ -232,11 +232,11 @@ class CarbonReporter:
232
232
  return self.carbon_intensity
233
233
 
234
234
  @staticmethod
235
- def get_supported_countries() -> List[str]:
235
+ def get_supported_countries() -> list[str]:
236
236
  # Get list of supported country ISO codes.
237
237
  return list(CARBON_INTENSITY_BY_COUNTRY.keys())
238
238
 
239
239
  @staticmethod
240
- def get_supported_cloud_regions(provider: str) -> List[str]:
240
+ def get_supported_cloud_regions(provider: str) -> list[str]:
241
241
  # Get list of supported cloud regions for a provider.
242
242
  return list(CLOUD_REGION_INTENSITY.get(provider.lower(), {}).keys())
@@ -4,9 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import time
6
6
  from datetime import datetime
7
- from typing import Optional
8
7
 
9
- from .base import EnergyMeter, EnergyMetrics, EnergyBackend
8
+ from .base import EnergyBackend, EnergyMeter, EnergyMetrics
10
9
 
11
10
 
12
11
  class CodeCarbonMeter(EnergyMeter):
@@ -15,7 +14,7 @@ class CodeCarbonMeter(EnergyMeter):
15
14
  def __init__(
16
15
  self,
17
16
  project_name: str = "greenmining",
18
- output_dir: Optional[str] = None,
17
+ output_dir: str | None = None,
19
18
  save_to_file: bool = False,
20
19
  ):
21
20
  # Initialize CodeCarbon energy meter.
@@ -24,13 +23,13 @@ class CodeCarbonMeter(EnergyMeter):
24
23
  self.output_dir = output_dir
25
24
  self.save_to_file = save_to_file
26
25
  self._tracker = None
27
- self._start_time: Optional[float] = None
26
+ self._start_time: float | None = None
28
27
  self._codecarbon_available = self._check_codecarbon()
29
28
 
30
29
  def _check_codecarbon(self) -> bool:
31
30
  # Check if CodeCarbon is installed.
32
31
  try:
33
- from codecarbon import EmissionsTracker
32
+ from codecarbon import EmissionsTracker # noqa: F401
34
33
 
35
34
  return True
36
35
  except ImportError:
@@ -123,4 +122,3 @@ class CodeCarbonMeter(EnergyMeter):
123
122
  start_time=datetime.fromtimestamp(self._start_time),
124
123
  end_time=datetime.fromtimestamp(end_time),
125
124
  )
126
-
@@ -3,12 +3,11 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- import time
7
6
  import platform
7
+ import time
8
8
  from datetime import datetime
9
- from typing import List, Optional
10
9
 
11
- from .base import EnergyMeter, EnergyMetrics, EnergyBackend
10
+ from .base import EnergyBackend, EnergyMeter, EnergyMetrics
12
11
 
13
12
 
14
13
  class CPUEnergyMeter(EnergyMeter):
@@ -23,7 +22,7 @@ class CPUEnergyMeter(EnergyMeter):
23
22
  "Windows": 65.0,
24
23
  }
25
24
 
26
- def __init__(self, tdp_watts: Optional[float] = None, sample_interval: float = 0.5):
25
+ def __init__(self, tdp_watts: float | None = None, sample_interval: float = 0.5):
27
26
  # Initialize CPU energy meter.
28
27
  # Args:
29
28
  # tdp_watts: CPU Thermal Design Power in watts (auto-detected if None)
@@ -31,15 +30,15 @@ class CPUEnergyMeter(EnergyMeter):
31
30
  super().__init__(EnergyBackend.CPU_METER)
32
31
  self.tdp_watts = tdp_watts or self._detect_tdp()
33
32
  self.sample_interval = sample_interval
34
- self._start_time: Optional[float] = None
35
- self._samples: List[float] = []
33
+ self._start_time: float | None = None
34
+ self._samples: list[float] = []
36
35
  self._platform = platform.system()
37
36
  self._psutil_available = self._check_psutil()
38
37
 
39
38
  def _check_psutil(self) -> bool:
40
39
  # Check if psutil is available.
41
40
  try:
42
- import psutil
41
+ import psutil # noqa: F401
43
42
 
44
43
  return True
45
44
  except ImportError:
@@ -2,13 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  import time
7
6
  from datetime import datetime
8
7
  from pathlib import Path
9
- from typing import Dict, List, Optional
10
8
 
11
- from .base import EnergyMeter, EnergyMetrics, EnergyBackend
9
+ from .base import EnergyBackend, EnergyMeter, EnergyMetrics
12
10
 
13
11
 
14
12
  class RAPLEnergyMeter(EnergyMeter):
@@ -19,10 +17,10 @@ class RAPLEnergyMeter(EnergyMeter):
19
17
  def __init__(self):
20
18
  # Initialize RAPL energy meter.
21
19
  super().__init__(EnergyBackend.RAPL)
22
- self._domains: Dict[str, Path] = {}
23
- self._start_energy: Dict[str, int] = {}
24
- self._start_time: Optional[float] = None
25
- self._power_samples: List[float] = []
20
+ self._domains: dict[str, Path] = {}
21
+ self._start_energy: dict[str, int] = {}
22
+ self._start_time: float | None = None
23
+ self._power_samples: list[float] = []
26
24
  self._discover_domains()
27
25
 
28
26
  def _discover_domains(self) -> None:
@@ -146,6 +144,6 @@ class RAPLEnergyMeter(EnergyMeter):
146
144
  end_time=datetime.fromtimestamp(end_time),
147
145
  )
148
146
 
149
- def get_available_domains(self) -> List[str]:
147
+ def get_available_domains(self) -> list[str]:
150
148
  # Get list of available RAPL domains.
151
149
  return list(self._domains.keys())
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass, field
6
- from typing import Optional
7
6
 
8
7
 
9
8
  @dataclass
@@ -14,7 +13,7 @@ class AggregatedStats:
14
13
  known_patterns: dict = field(default_factory=dict)
15
14
  repositories: list[dict] = field(default_factory=list)
16
15
  languages: dict = field(default_factory=dict)
17
- timestamp: Optional[str] = None
16
+ timestamp: str | None = None
18
17
 
19
18
  def to_dict(self) -> dict:
20
19
  # Convert to dictionary.
@@ -27,6 +26,6 @@ class AggregatedStats:
27
26
  }
28
27
 
29
28
  @classmethod
30
- def from_dict(cls, data: dict) -> "AggregatedStats":
29
+ def from_dict(cls, data: dict) -> AggregatedStats:
31
30
  # Create from dictionary.
32
31
  return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
@@ -44,12 +44,12 @@ class Commit:
44
44
  }
45
45
 
46
46
  @classmethod
47
- def from_dict(cls, data: dict) -> "Commit":
47
+ def from_dict(cls, data: dict) -> Commit:
48
48
  # Create from dictionary.
49
49
  return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
50
50
 
51
51
  @classmethod
52
- def from_pydriller_commit(cls, commit, repo_name: str) -> "Commit":
52
+ def from_pydriller_commit(cls, commit, repo_name: str) -> Commit:
53
53
  # Create from PyDriller commit object.
54
54
  return cls(
55
55
  commit_id=commit.hash,
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass, field
6
- from typing import Optional
7
6
 
8
7
 
9
8
  @dataclass
@@ -16,21 +15,21 @@ class Repository:
16
15
  full_name: str
17
16
  url: str
18
17
  clone_url: str
19
- language: Optional[str]
18
+ language: str | None
20
19
  stars: int
21
20
  forks: int
22
21
  watchers: int
23
22
  open_issues: int
24
23
  last_updated: str
25
24
  created_at: str
26
- description: Optional[str]
25
+ description: str | None
27
26
  main_branch: str
28
27
  topics: list[str] = field(default_factory=list)
29
28
  size: int = 0
30
29
  has_issues: bool = True
31
30
  has_wiki: bool = True
32
31
  archived: bool = False
33
- license: Optional[str] = None
32
+ license: str | None = None
34
33
 
35
34
  def to_dict(self) -> dict:
36
35
  # Convert to dictionary.
@@ -59,12 +58,12 @@ class Repository:
59
58
  }
60
59
 
61
60
  @classmethod
62
- def from_dict(cls, data: dict) -> "Repository":
61
+ def from_dict(cls, data: dict) -> Repository:
63
62
  # Create from dictionary.
64
63
  return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
65
64
 
66
65
  @classmethod
67
- def from_github_repo(cls, repo, repo_id: int) -> "Repository":
66
+ def from_github_repo(cls, repo, repo_id: int) -> Repository:
68
67
  # Create from PyGithub repository object.
69
68
  return cls(
70
69
  repo_id=repo_id,
@@ -5,10 +5,10 @@ from .data_aggregator import DataAggregator
5
5
  from .data_analyzer import DataAnalyzer
6
6
  from .github_graphql_fetcher import GitHubGraphQLFetcher
7
7
  from .local_repo_analyzer import (
8
- LocalRepoAnalyzer,
9
8
  CommitAnalysis,
10
- RepositoryAnalysis,
9
+ LocalRepoAnalyzer,
11
10
  MethodMetrics,
11
+ RepositoryAnalysis,
12
12
  SourceCodeChange,
13
13
  )
14
14
  from .reports import ReportGenerator
@@ -2,7 +2,7 @@
2
2
 
3
3
  import json
4
4
  import time
5
- from typing import Any, Dict, List, Optional
5
+ from typing import Any, Optional
6
6
 
7
7
  import requests
8
8
 
@@ -30,12 +30,12 @@ class GitHubGraphQLFetcher:
30
30
  keywords: str = "microservices",
31
31
  max_repos: int = 100,
32
32
  min_stars: int = 100,
33
- languages: Optional[List[str]] = None,
33
+ languages: Optional[list[str]] = None,
34
34
  created_after: Optional[str] = None,
35
35
  created_before: Optional[str] = None,
36
36
  pushed_after: Optional[str] = None,
37
37
  pushed_before: Optional[str] = None,
38
- ) -> List[Repository]:
38
+ ) -> list[Repository]:
39
39
  # Search GitHub repositories using GraphQL.
40
40
  #
41
41
  # Args:
@@ -172,7 +172,7 @@ class GitHubGraphQLFetcher:
172
172
  self,
173
173
  keywords: str,
174
174
  min_stars: int,
175
- languages: Optional[List[str]],
175
+ languages: Optional[list[str]],
176
176
  created_after: Optional[str],
177
177
  created_before: Optional[str],
178
178
  pushed_after: Optional[str],
@@ -201,7 +201,7 @@ class GitHubGraphQLFetcher:
201
201
 
202
202
  return " ".join(query_parts)
203
203
 
204
- def _execute_query(self, query: str, variables: Dict[str, Any]) -> Dict[str, Any]:
204
+ def _execute_query(self, query: str, variables: dict[str, Any]) -> dict[str, Any]:
205
205
  # Execute GraphQL query.
206
206
  payload = {"query": query, "variables": variables}
207
207
 
@@ -212,7 +212,7 @@ class GitHubGraphQLFetcher:
212
212
  response.raise_for_status()
213
213
  return response.json()
214
214
 
215
- def _parse_repository(self, node: Dict[str, Any], repo_id: int = 0) -> Repository:
215
+ def _parse_repository(self, node: dict[str, Any], repo_id: int = 0) -> Repository:
216
216
  # Parse GraphQL repository node to Repository object.
217
217
  full_name = node.get("nameWithOwner", "")
218
218
  owner = full_name.split("/")[0] if "/" in full_name else ""
@@ -252,7 +252,7 @@ class GitHubGraphQLFetcher:
252
252
 
253
253
  def get_repository_commits(
254
254
  self, owner: str, name: str, max_commits: int = 100
255
- ) -> List[Dict[str, Any]]:
255
+ ) -> list[dict[str, Any]]:
256
256
  # Fetch commits for a specific repository using GraphQL.
257
257
  #
258
258
  # Args:
@@ -341,7 +341,7 @@ class GitHubGraphQLFetcher:
341
341
 
342
342
  return commits
343
343
 
344
- def save_results(self, repositories: List[Repository], output_file: str):
344
+ def save_results(self, repositories: list[Repository], output_file: str):
345
345
  # Save repositories to JSON file.
346
346
  data = {
347
347
  "total_repositories": len(repositories),
@@ -5,12 +5,11 @@ from __future__ import annotations
5
5
  import os
6
6
  import re
7
7
  import shutil
8
- import tempfile
9
8
  from concurrent.futures import ThreadPoolExecutor, as_completed
10
9
  from dataclasses import dataclass, field
11
10
  from datetime import datetime, timedelta
12
11
  from pathlib import Path
13
- from typing import Any, Dict, List, Optional
12
+ from typing import Any
14
13
 
15
14
  from pydriller import Repository
16
15
  from pydriller.metrics.process.change_set import ChangeSet
@@ -22,7 +21,7 @@ from pydriller.metrics.process.history_complexity import HistoryComplexity
22
21
  from pydriller.metrics.process.hunks_count import HunksCount
23
22
  from pydriller.metrics.process.lines_count import LinesCount
24
23
 
25
- from greenmining.gsf_patterns import get_pattern_by_keywords, is_green_aware, GSF_PATTERNS
24
+ from greenmining.gsf_patterns import GSF_PATTERNS, get_pattern_by_keywords, is_green_aware
26
25
  from greenmining.utils import colored_print
27
26
 
28
27
 
@@ -40,7 +39,7 @@ class MethodMetrics:
40
39
  start_line: int = 0
41
40
  end_line: int = 0
42
41
 
43
- def to_dict(self) -> Dict[str, Any]:
42
+ def to_dict(self) -> dict[str, Any]:
44
43
  return {
45
44
  "name": self.name,
46
45
  "long_name": self.long_name,
@@ -59,14 +58,14 @@ class SourceCodeChange:
59
58
  # Source code before/after a commit for refactoring detection.
60
59
 
61
60
  filename: str
62
- source_code_before: Optional[str] = None
63
- source_code_after: Optional[str] = None
64
- diff: Optional[str] = None
61
+ source_code_before: str | None = None
62
+ source_code_after: str | None = None
63
+ diff: str | None = None
65
64
  added_lines: int = 0
66
65
  deleted_lines: int = 0
67
66
  change_type: str = "" # ADD, DELETE, MODIFY, RENAME
68
67
 
69
- def to_dict(self) -> Dict[str, Any]:
68
+ def to_dict(self) -> dict[str, Any]:
70
69
  return {
71
70
  "filename": self.filename,
72
71
  "source_code_before": self.source_code_before,
@@ -88,18 +87,18 @@ class CommitAnalysis:
88
87
  author_email: str
89
88
  date: datetime
90
89
  green_aware: bool
91
- gsf_patterns_matched: List[str]
90
+ gsf_patterns_matched: list[str]
92
91
  pattern_count: int
93
- pattern_details: List[Dict[str, Any]]
92
+ pattern_details: list[dict[str, Any]]
94
93
  confidence: str
95
- files_modified: List[str]
94
+ files_modified: list[str]
96
95
  insertions: int
97
96
  deletions: int
98
97
 
99
98
  # PyDriller DMM metrics
100
- dmm_unit_size: Optional[float] = None
101
- dmm_unit_complexity: Optional[float] = None
102
- dmm_unit_interfacing: Optional[float] = None
99
+ dmm_unit_size: float | None = None
100
+ dmm_unit_complexity: float | None = None
101
+ dmm_unit_interfacing: float | None = None
103
102
 
104
103
  # Structural metrics (Lizard)
105
104
  total_nloc: int = 0
@@ -108,16 +107,16 @@ class CommitAnalysis:
108
107
  methods_count: int = 0
109
108
 
110
109
  # Method-level analysis (Phase 3.2)
111
- methods: List[MethodMetrics] = field(default_factory=list)
110
+ methods: list[MethodMetrics] = field(default_factory=list)
112
111
 
113
112
  # Source code access (Phase 3.3)
114
- source_changes: List[SourceCodeChange] = field(default_factory=list)
113
+ source_changes: list[SourceCodeChange] = field(default_factory=list)
115
114
 
116
115
  # Energy metrics (Phase 2.2 - populated when energy_tracking=True)
117
- energy_joules: Optional[float] = None
118
- energy_watts_avg: Optional[float] = None
116
+ energy_joules: float | None = None
117
+ energy_watts_avg: float | None = None
119
118
 
120
- def to_dict(self) -> Dict[str, Any]:
119
+ def to_dict(self) -> dict[str, Any]:
121
120
  # Convert to dictionary.
122
121
  result = {
123
122
  "commit_hash": self.hash,
@@ -164,11 +163,11 @@ class RepositoryAnalysis:
164
163
  total_commits: int
165
164
  green_commits: int
166
165
  green_commit_rate: float
167
- commits: List[CommitAnalysis] = field(default_factory=list)
168
- process_metrics: Dict[str, Any] = field(default_factory=dict)
169
- energy_metrics: Optional[Dict[str, Any]] = None
166
+ commits: list[CommitAnalysis] = field(default_factory=list)
167
+ process_metrics: dict[str, Any] = field(default_factory=dict)
168
+ energy_metrics: dict[str, Any] | None = None
170
169
 
171
- def to_dict(self) -> Dict[str, Any]:
170
+ def to_dict(self) -> dict[str, Any]:
172
171
  # Convert to dictionary.
173
172
  result = {
174
173
  "url": self.url,
@@ -190,21 +189,22 @@ class LocalRepoAnalyzer:
190
189
 
191
190
  def __init__(
192
191
  self,
193
- clone_path: Optional[Path] = None,
192
+ clone_path: Path | None = None,
194
193
  max_commits: int = 500,
195
194
  days_back: int = 730,
196
195
  skip_merges: bool = True,
197
196
  compute_process_metrics: bool = True,
198
197
  cleanup_after: bool = True,
199
- ssh_key_path: Optional[str] = None,
200
- github_token: Optional[str] = None,
198
+ ssh_key_path: str | None = None,
199
+ github_token: str | None = None,
201
200
  energy_tracking: bool = False,
202
201
  energy_backend: str = "rapl",
203
202
  method_level_analysis: bool = False,
204
203
  include_source_code: bool = False,
205
204
  process_metrics: str = "standard",
206
- since_date: Optional[datetime] = None,
207
- to_date: Optional[datetime] = None,
205
+ since_date: datetime | None = None,
206
+ to_date: datetime | None = None,
207
+ commit_order: str = "newest_first",
208
208
  ):
209
209
  # Initialize the local repository analyzer.
210
210
  # Args:
@@ -221,6 +221,7 @@ class LocalRepoAnalyzer:
221
221
  # method_level_analysis: Extract per-method metrics via Lizard
222
222
  # include_source_code: Include source code before/after in results
223
223
  # process_metrics: "standard" or "full" PyDriller process metrics
224
+ # commit_order: "newest_first" (default) or "oldest_first"
224
225
  self.clone_path = clone_path or Path.cwd() / "greenmining_repos"
225
226
  self.clone_path.mkdir(parents=True, exist_ok=True)
226
227
  self.max_commits = max_commits
@@ -230,6 +231,7 @@ class LocalRepoAnalyzer:
230
231
  self.skip_merges = skip_merges
231
232
  self.compute_process_metrics = compute_process_metrics
232
233
  self.cleanup_after = cleanup_after
234
+ self.commit_order = commit_order
233
235
  self.gsf_patterns = GSF_PATTERNS
234
236
 
235
237
  # Phase 1.3: Private repository support
@@ -269,7 +271,7 @@ class LocalRepoAnalyzer:
269
271
  return url.replace("https://", f"https://x-access-token:{self.github_token}@")
270
272
  return url
271
273
 
272
- def _setup_ssh_env(self) -> Dict[str, str]:
274
+ def _setup_ssh_env(self) -> dict[str, str]:
273
275
  # Set up SSH environment for private repository cloning.
274
276
  env = os.environ.copy()
275
277
  if self.ssh_key_path:
@@ -294,10 +296,10 @@ class LocalRepoAnalyzer:
294
296
 
295
297
  raise ValueError(f"Could not parse GitHub URL: {url}")
296
298
 
297
- def _get_pattern_details(self, matched_patterns: List[str]) -> List[Dict[str, Any]]:
299
+ def _get_pattern_details(self, matched_patterns: list[str]) -> list[dict[str, Any]]:
298
300
  # Get detailed pattern information.
299
301
  details = []
300
- for pattern_id, pattern in self.gsf_patterns.items():
302
+ for _pattern_id, pattern in self.gsf_patterns.items():
301
303
  if pattern["name"] in matched_patterns:
302
304
  details.append(
303
305
  {
@@ -309,7 +311,7 @@ class LocalRepoAnalyzer:
309
311
  )
310
312
  return details
311
313
 
312
- def _extract_method_metrics(self, commit) -> List[MethodMetrics]:
314
+ def _extract_method_metrics(self, commit) -> list[MethodMetrics]:
313
315
  # Extract per-method metrics from modified files using Lizard (via PyDriller).
314
316
  methods = []
315
317
  try:
@@ -333,7 +335,7 @@ class LocalRepoAnalyzer:
333
335
  pass
334
336
  return methods
335
337
 
336
- def _extract_source_changes(self, commit) -> List[SourceCodeChange]:
338
+ def _extract_source_changes(self, commit) -> list[SourceCodeChange]:
337
339
  # Extract source code before/after for each modified file.
338
340
  changes = []
339
341
  try:
@@ -459,6 +461,8 @@ class LocalRepoAnalyzer:
459
461
  }
460
462
  if self.to_date:
461
463
  repo_config["to"] = self.to_date
464
+ if self.commit_order == "oldest_first":
465
+ repo_config["order"] = "reverse"
462
466
 
463
467
  # Use owner_repo format for unique directory names (avoids collisions
464
468
  # when multiple repos share the same name, e.g. open-android/Android
@@ -481,6 +485,7 @@ class LocalRepoAnalyzer:
481
485
  if self.energy_tracking:
482
486
  try:
483
487
  from greenmining.energy.base import get_energy_meter
488
+
484
489
  energy_meter = get_energy_meter(self.energy_backend)
485
490
  energy_meter.start()
486
491
  except Exception:
@@ -552,7 +557,7 @@ class LocalRepoAnalyzer:
552
557
  colored_print(f" Cleaning up: {clone_parent}", "cyan")
553
558
  shutil.rmtree(clone_parent, ignore_errors=True)
554
559
 
555
- def _compute_process_metrics(self, repo_path: str) -> Dict[str, Any]:
560
+ def _compute_process_metrics(self, repo_path: str) -> dict[str, Any]:
556
561
  # Compute PyDriller process metrics for the repository.
557
562
  metrics = {}
558
563
  since_date = datetime.now() - timedelta(days=self.days_back)
@@ -619,10 +624,10 @@ class LocalRepoAnalyzer:
619
624
 
620
625
  def analyze_repositories(
621
626
  self,
622
- urls: List[str],
627
+ urls: list[str],
623
628
  parallel_workers: int = 1,
624
629
  output_format: str = "dict",
625
- ) -> List[RepositoryAnalysis]:
630
+ ) -> list[RepositoryAnalysis]:
626
631
  # Analyze multiple repositories from URLs.
627
632
  # Args:
628
633
  # urls: List of repository URLs to analyze
@@ -632,7 +637,7 @@ class LocalRepoAnalyzer:
632
637
  return self._analyze_sequential(urls)
633
638
  return self._analyze_parallel(urls, parallel_workers)
634
639
 
635
- def _analyze_sequential(self, urls: List[str]) -> List[RepositoryAnalysis]:
640
+ def _analyze_sequential(self, urls: list[str]) -> list[RepositoryAnalysis]:
636
641
  # Analyze repositories sequentially.
637
642
  results = []
638
643
  for i, url in enumerate(urls, 1):
@@ -648,7 +653,7 @@ class LocalRepoAnalyzer:
648
653
  continue
649
654
  return results
650
655
 
651
- def _analyze_parallel(self, urls: List[str], max_workers: int) -> List[RepositoryAnalysis]:
656
+ def _analyze_parallel(self, urls: list[str], max_workers: int) -> list[RepositoryAnalysis]:
652
657
  # Analyze repositories in parallel using thread pool.
653
658
  results = []
654
659
  colored_print(f"\n Analyzing {len(urls)} repositories with {max_workers} workers", "cyan")
@@ -660,7 +665,9 @@ class LocalRepoAnalyzer:
660
665
  try:
661
666
  result = future.result()
662
667
  if result.total_commits == 0:
663
- colored_print(f" Skipping {result.name}: no commits in date range", "yellow")
668
+ colored_print(
669
+ f" Skipping {result.name}: no commits in date range", "yellow"
670
+ )
664
671
  continue
665
672
  results.append(result)
666
673
  colored_print(f" Completed: {result.name}", "green")
@@ -366,7 +366,7 @@ No novel microservice-specific green practices were automatically detected. Manu
366
366
  if green_vs_nongreen:
367
367
  cohens_d = green_vs_nongreen.get("cohens_d", 0)
368
368
  magnitude = green_vs_nongreen.get("magnitude", "negligible")
369
- sections.append(f"**Green vs Non-Green Pattern Usage:**")
369
+ sections.append("**Green vs Non-Green Pattern Usage:**")
370
370
  sections.append(f"- Cohen's d: {cohens_d:.3f}")
371
371
  sections.append(f"- Effect magnitude: {magnitude.capitalize()}")
372
372
  sections.append("")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: greenmining
3
- Version: 1.2.3
3
+ Version: 1.2.5
4
4
  Summary: An empirical Python library for Mining Software Repositories (MSR) in Green IT research
5
5
  Author-email: Adam Bouafia <a.bouafia@student.vu.nl>
6
6
  License: MIT
@@ -0,0 +1,34 @@
1
+ greenmining/__init__.py,sha256=jKktkjxKacTGmUoXMwfuW7DcyUKepUkTCNLjTmU2Hvc,4496
2
+ greenmining/__main__.py,sha256=NYOVS7D4w2XDLn6SyXHXPKE5GrNGOeoWSTb_KazgK5c,590
3
+ greenmining/gsf_patterns.py,sha256=UvNJPY3HlAx1SicwUqci40TlLg8lCL0tszSOH4haxQs,55921
4
+ greenmining/utils.py,sha256=-dnLUw9taCzvQ2dk6uc66GAohOFiXJFKs9TLSEPk5kM,2893
5
+ greenmining/analyzers/__init__.py,sha256=FExlzEE2c2TZ82wqTh1il5qcZFhUDKBtB_HB-aC4ynA,416
6
+ greenmining/analyzers/code_diff_analyzer.py,sha256=KVvIYMmTvrjrH0n1EjyXuSfqwWPmlU8mAZ0F4Q1nYaQ,10939
7
+ greenmining/analyzers/metrics_power_correlator.py,sha256=4p2E_JRFjDSRLn10V-slZKvgNFR3H-8OXF6waBN-7DU,5928
8
+ greenmining/analyzers/statistical_analyzer.py,sha256=hMUNC8IjvN30f365CBtUqhrExNKGMPMVb1uKVEdBvwU,5930
9
+ greenmining/analyzers/temporal_analyzer.py,sha256=OoT1lUTimocag8TGgpTMHVdfiyEqnM2yiYA7QeHWZ3g,14203
10
+ greenmining/controllers/__init__.py,sha256=UiAT6zBvC1z_9cJWfzq1cLA0I4r9b2vURHipj8oDczI,180
11
+ greenmining/controllers/repository_controller.py,sha256=8XzeFIpaYzPznlQRaftHxkpBdNmyzchxU40yolZcodw,6011
12
+ greenmining/energy/__init__.py,sha256=WR_BvnHrUmEyDWaOPVpYap_kpat13K-mgtmvMAtXPZQ,558
13
+ greenmining/energy/base.py,sha256=G_II_7tgITVJtXZTLFvB7oMmhK5nJDem1hHkOEcifF0,5850
14
+ greenmining/energy/carbon_reporter.py,sha256=k41M6vcDuEYVK4KnsG2DrT9jV5oO82nJzAfIWz1r6Z4,8261
15
+ greenmining/energy/codecarbon_meter.py,sha256=zhMsZXdk2WRLZf3mU6p7FF2uAn5YlNogqhuMOCm6Xbs,4193
16
+ greenmining/energy/cpu_meter.py,sha256=g7oJzcpbEW-qp-9uUGhHduCj2-RBgry9VpsPIiYOKYE,4975
17
+ greenmining/energy/rapl.py,sha256=CpFa_j_g6UKf4f82CH8DIBZRRdRSqg_4Og51D6kMYVU,5239
18
+ greenmining/models/__init__.py,sha256=2hkB0quhMePvvA1AkYfj5uiF_HyGtXVxn0BU-5m_oSg,302
19
+ greenmining/models/aggregated_stats.py,sha256=il5c0pHF2PAYywbVwbod-SNcdz0q80XJ0AFKo2Gmits,971
20
+ greenmining/models/analysis_result.py,sha256=YICTCEcrJxZ1R8Xaio3AZOjCGwMzC_62BMAL0J_XY1w,1509
21
+ greenmining/models/commit.py,sha256=tkjMXXoMEAPxVR7M9Bf95gUSLqQF4GLeCs6_bBlK1go,2411
22
+ greenmining/models/repository.py,sha256=qsFoBmtmDn71g8WjHel5Zu9Ny4ij98E7n-cgcFNkJWI,2809
23
+ greenmining/services/__init__.py,sha256=QBsyLE5vNcQpyVaT1DD_ThS4pJ7pb4Obl-zUpp2GAnM,690
24
+ greenmining/services/commit_extractor.py,sha256=qBM9QpGzPZRmGMFufJ6gP8eWIuufTowLX8mQxqZwyEU,6996
25
+ greenmining/services/data_aggregator.py,sha256=BU_HUb-8c0n0sa_7VZRB8jIVnaVhRLf-E6KA4ASh-08,19427
26
+ greenmining/services/data_analyzer.py,sha256=0XqW-slrnt7RotrHDweOqKtoN8XIA7y6p7s2Jau6cMg,7431
27
+ greenmining/services/github_graphql_fetcher.py,sha256=WhSbQGMdkb0D4uLcMKW6xZK77c5AkW-nZf718issap4,11527
28
+ greenmining/services/local_repo_analyzer.py,sha256=Cq6NixciZmqDuWpU5976TuhGiaGFNnvvz4Rs11bq2Ug,25891
29
+ greenmining/services/reports.py,sha256=HQo52mdhwXGZgRV1BWaIA4WSs6N3Q2_Wgdsbb2RSQZU,23218
30
+ greenmining-1.2.5.dist-info/licenses/LICENSE,sha256=M7ma3JHGeiIZIs3ea0HTcFl_wLFPX2NZElUliYs4bCA,1083
31
+ greenmining-1.2.5.dist-info/METADATA,sha256=WVoukYReWst87-OVgNEsi3vXaI51Dsh4h54jq3gieYI,10522
32
+ greenmining-1.2.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
33
+ greenmining-1.2.5.dist-info/top_level.txt,sha256=nreXgXxZIWI-42yQknQ0HXtUrFnzZ8N1ra4Mdy2KcsI,12
34
+ greenmining-1.2.5.dist-info/RECORD,,
@@ -1,34 +0,0 @@
1
- greenmining/__init__.py,sha256=2giUY7aYzaq81FQ1t2GsJAvuIMmBK9VYkPW63c0FbCw,4165
2
- greenmining/__main__.py,sha256=NYOVS7D4w2XDLn6SyXHXPKE5GrNGOeoWSTb_KazgK5c,590
3
- greenmining/gsf_patterns.py,sha256=UvNJPY3HlAx1SicwUqci40TlLg8lCL0tszSOH4haxQs,55921
4
- greenmining/utils.py,sha256=-dnLUw9taCzvQ2dk6uc66GAohOFiXJFKs9TLSEPk5kM,2893
5
- greenmining/analyzers/__init__.py,sha256=wnBrn8EyAHG_qnesOPAYkZyc-XigXWy2pI3bMeIoLH4,416
6
- greenmining/analyzers/code_diff_analyzer.py,sha256=1dk68R3O0RZG8gx1cm9B_UlZ1Uwyb_Q3oScRbCVx4tM,10950
7
- greenmining/analyzers/metrics_power_correlator.py,sha256=MgKXAIYjNihzzyilCd88_AMjZP9sdC6NkCAVbrvvOus,5957
8
- greenmining/analyzers/statistical_analyzer.py,sha256=PA0w0sytRmMO6N1a2iH7VdA6Icg4DcyBLFXOGq7PepY,5942
9
- greenmining/analyzers/temporal_analyzer.py,sha256=JfTcAoI20oCFMehGrSRnDqhJTXI-RUbdCTMwDOTW9-g,14259
10
- greenmining/controllers/__init__.py,sha256=UiAT6zBvC1z_9cJWfzq1cLA0I4r9b2vURHipj8oDczI,180
11
- greenmining/controllers/repository_controller.py,sha256=sjfbDhyRY59MsKLw0dkxzpe1QZKtm9ScO4E8VFYZy9A,6041
12
- greenmining/energy/__init__.py,sha256=GoCYh7hitWBoPMtan1HF1yezCHi7o4sa_YUJgGkeJc8,558
13
- greenmining/energy/base.py,sha256=3hIPgc4B0Nz9V7DTh2Xd6trDRtmozUBBpa5UWRuWzcw,5918
14
- greenmining/energy/carbon_reporter.py,sha256=bKIFlLhHfYzI4DBu_ff4GW1Psz4oSCAF4NmzQb-EShA,8298
15
- greenmining/energy/codecarbon_meter.py,sha256=8obsfiJi0V3R_2BMHjTQCZSN52YPvFn5d9q_MKOZVb4,4214
16
- greenmining/energy/cpu_meter.py,sha256=GmUZsOIzWnAWcuSW4RndDdgszDHzqnBjAIeLBgelZ0w,5001
17
- greenmining/energy/rapl.py,sha256=b63M1mS7uF9Uo0vFi0z7Qwdo56U1TqxIYQXINhYp9Jo,5292
18
- greenmining/models/__init__.py,sha256=2hkB0quhMePvvA1AkYfj5uiF_HyGtXVxn0BU-5m_oSg,302
19
- greenmining/models/aggregated_stats.py,sha256=CZxjwXswvtmYPwpcbodLUsZpsbsNKBDIqvU9DpFO_t0,1004
20
- greenmining/models/analysis_result.py,sha256=YICTCEcrJxZ1R8Xaio3AZOjCGwMzC_62BMAL0J_XY1w,1509
21
- greenmining/models/commit.py,sha256=LCwDcRu4-BeCJQdk590oQNZZZM9t8W9FlaHlo9DCVmc,2415
22
- greenmining/models/repository.py,sha256=MUeCOtVMOsU4Oa_BBoB163Ij5BKytTKwbzoGORJx4rU,2850
23
- greenmining/services/__init__.py,sha256=ZEMOVut0KRdume_vz58beSNps3YgeoGBXmUjEqNgIhc,690
24
- greenmining/services/commit_extractor.py,sha256=qBM9QpGzPZRmGMFufJ6gP8eWIuufTowLX8mQxqZwyEU,6996
25
- greenmining/services/data_aggregator.py,sha256=BU_HUb-8c0n0sa_7VZRB8jIVnaVhRLf-E6KA4ASh-08,19427
26
- greenmining/services/data_analyzer.py,sha256=0XqW-slrnt7RotrHDweOqKtoN8XIA7y6p7s2Jau6cMg,7431
27
- greenmining/services/github_graphql_fetcher.py,sha256=ZklXdEAc60KeFL83zRYMwW_-2OwMKpfPY7Wrifl0D50,11539
28
- greenmining/services/local_repo_analyzer.py,sha256=RfhIdI6GBmHndBl1zZQwUlEuVIO55PILIE_ShP6eQtA,25668
29
- greenmining/services/reports.py,sha256=nhJuYiA5tPD_9AjtgSLEnrpW3x15sZXrwIxpxQEBbh0,23219
30
- greenmining-1.2.3.dist-info/licenses/LICENSE,sha256=M7ma3JHGeiIZIs3ea0HTcFl_wLFPX2NZElUliYs4bCA,1083
31
- greenmining-1.2.3.dist-info/METADATA,sha256=33ItQrNW_1q2J4iuefPKJlL4uUZmxO4WKP8ZmN9ZnUg,10522
32
- greenmining-1.2.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
33
- greenmining-1.2.3.dist-info/top_level.txt,sha256=nreXgXxZIWI-42yQknQ0HXtUrFnzZ8N1ra4Mdy2KcsI,12
34
- greenmining-1.2.3.dist-info/RECORD,,