Metadata-Version: 2.4
Name: sd-metrics-lib
Version: 2.0
Summary: Library to calculate various metrics of software development process
Author-email: Igor Zarvanskyi <iclutcher@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/clutcher/sd-metrics-lib
Project-URL: Bug Tracker, https://github.com/clutcher/sd-metrics-lib/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-dateutil>=2.8
Provides-Extra: jira
Requires-Dist: atlassian-python-api>=3.0; extra == "jira"
Provides-Extra: azure
Requires-Dist: azure-devops>=7.1.0b4; extra == "azure"
Requires-Dist: msrest>=0.7; extra == "azure"
Dynamic: license-file

# sd-metrics-lib

Python library for calculating various metrics related to the software development process. Provides developer and team velocity
calculations based on data from Jira and Azure DevOps. Metrics calculation classes use interfaces, so the library can be easily extended
with other data providers (e.g., Trello, Asana) from application code.

## Architecture and API reference

### Overview

This library separates metric calculation from data sourcing. Calculators operate on abstract provider interfaces so you can plug in Jira, Azure DevOps, or your own sources. Below is a structured overview by package with links to the key classes you will use.

### Calculators

- Module: `calculators.metrics_calculator`
    - `MetricCalculator` (abstract): Base interface for all metric calculators (`calculate()`).
- Module: `calculators.velocity_calculator`
    - `AbstractMetricCalculator` (abstract): Adds lazy extraction and shared `calculate()` workflow.
    - `UserVelocityCalculator`: Per-user velocity (story points per time unit). Requires `IssueProvider`, `StoryPointExtractor`, `WorklogExtractor`.
    - `GeneralizedTeamVelocityCalculator`: Team velocity (total story points per time unit). Requires `IssueProvider`, `StoryPointExtractor`, `IssueTotalSpentTimeExtractor`.

### Data providers

- Module: `data_providers.issue_provider`
    - `IssueProvider` (abstract): Fetches a list of issues/work items.
    - `ProxyIssueProvider`: Wraps a pre-fetched list of issues (useful for tests/custom sources).
    - `CachingIssueProvider`: Caches results of any `IssueProvider`. Cache key is built from `provider.query` and `provider.additional_fields`; works with any dict-like cache (e.g., `cachetools.TTLCache`).
- Module: `data_providers.story_point_extractor`
    - `StoryPointExtractor` (abstract)
    - `ConstantStoryPointExtractor`: Returns a constant number of story points.
- Module: `data_providers.worklog_extractor`
    - `WorklogExtractor` (abstract): Returns mapping `user -> seconds` for an issue.
    - `IssueTotalSpentTimeExtractor` (abstract): Returns total time-in-seconds spent on an issue.
    - `ChainedWorklogExtractor`: Tries extractors in order and returns the first non-empty result.
- Module: `data_providers.worktime_extractor`
    - `WorkTimeExtractor` (abstract)
    - `SimpleWorkTimeExtractor`: Heuristic working-time calculator (caps to a day; ignores < 15 min intervals).
    - `BoundarySimpleWorkTimeExtractor`: Limits calculation to a boundary window, then delegates to `SimpleWorkTimeExtractor`.
- Module: `data_providers.abstract_worklog_extractor`
    - `AbstractStatusChangeWorklogExtractor` (abstract): Derives work time from assignment/status change history; attributes time to assignee and respects optional user filters and `WorkTimeExtractor`.

#### Jira

- Module: `data_providers.jira.issue_provider`
    - `JiraIssueProvider`: Fetch issues by `JQL` via `atlassian-python-api`; supports paging and optional `ThreadPoolExecutor`.
- Module: `data_providers.jira.story_point_extractor`
    - `JiraCustomFieldStoryPointExtractor`: Reads a numeric custom field; supports default value.
    - `JiraTShirtStoryPointExtractor`: Maps T-shirt sizes (e.g., `S`/`M`/`L`) to numbers from a custom field.
- Module: `data_providers.jira.worklog_extractor`
    - `JiraWorklogExtractor`: Aggregates time from native Jira worklogs (optionally includes subtasks); optional user filter.
    - `JiraStatusChangeWorklogExtractor`: Derives time from changelog (status/assignee changes); supports username vs `accountId` and status names vs codes; uses a `WorkTimeExtractor`.
    - `JiraResolutionTimeIssueTotalSpentTimeExtractor`: Total time from `created` to `resolutiondate`.

#### Azure DevOps

- Module: `data_providers.azure.issue_provider`
    - `AzureIssueProvider`: Executes `WIQL`; fetches work items in pages (sync or `ThreadPoolExecutor`); can expand updates for status-change-based calculations.
- Module: `data_providers.azure.story_point_extractor`
    - `AzureStoryPointExtractor`: Reads story points from a field (default `Microsoft.VSTS.Scheduling.StoryPoints`); robust parsing with default.
- Module: `data_providers.azure.worklog_extractor`
    - `AzureStatusChangeWorklogExtractor`: Derives per-user time from work item updates (assignment/state changes); supports status filters; uses `WorkTimeExtractor`.
    - `AzureIssueTotalSpentTimeExtractor`: Total time from `System.CreatedDate` to `Microsoft.VSTS.Common.ClosedDate`.

### Utilities

- Module: `data_providers.utils.enums`
    - `VelocityTimeUnit` (Enum): values `HOUR`, `DAY`, `WEEK`, `MONTH`
    - `HealthStatus` (Enum): values `GREEN`, `YELLOW`, `RED`
    - `SeniorityLevel` (Enum): values `JUNIOR`, `MIDDLE`, `SENIOR`
- Module: `data_providers.utils.query_utils`
    - `JiraIssueSearchQueryBuilder`: Builder for `JQL` (project, status, date range, type)
    - `TimeRangeGenerator`: Iterator producing date ranges for the requested `VelocityTimeUnit`
- Module: `data_providers.utils.story_point_utils`
    - `TShirtMapping`: Helper to convert between T-shirt sizes (`XS`/`S`/`M`/`L`/`XL`) and story points using default mapping `xs=1`, `s=5`, `m=8`, `l=13`, `xl=21`.
      Methods: `convert_into_points(size: str) -> int`, `convert_into_size(story_point: int) -> str`.
- Module: `calculators.utils.time_utils`
    - `convert_time(spent_time_in_seconds, VelocityTimeUnit) -> float`
    - `get_seconds_in_day() -> int`
    - Constants: `SECONDS_IN_HOUR`, `WORKING_HOURS_PER_DAY`, `WORKING_DAYS_PER_WEEK`, `WORKING_WEEKS_IN_MONTH`, `WEEKDAY_FRIDAY`

### Public API exports (import shortcuts)

- `calculators.__init__`: exports `UserVelocityCalculator`, `GeneralizedTeamVelocityCalculator`
- `data_providers.__init__`: exports `IssueProvider`, `ProxyIssueProvider`, `CachingIssueProvider`, `StoryPointExtractor`, `WorklogExtractor`, `ChainedWorklogExtractor`, `IssueTotalSpentTimeExtractor`
- `data_providers.jira.__init__`: exports `JiraIssueProvider`, `JiraCustomFieldStoryPointExtractor`, `JiraTShirtStoryPointExtractor`, `JiraWorklogExtractor`, `JiraStatusChangeWorklogExtractor`, `JiraResolutionTimeIssueTotalSpentTimeExtractor`
- `data_providers.azure.__init__`: exports `AzureIssueProvider`, `AzureStoryPointExtractor`, `AzureStatusChangeWorklogExtractor`, `AzureIssueTotalSpentTimeExtractor`
- `data_providers.utils.__init__`: exports `VelocityTimeUnit`, `HealthStatus`, `SeniorityLevel`, `JiraIssueSearchQueryBuilder`, `TimeRangeGenerator`, `TShirtMapping`

## Installation

Install core library:

```bash
pip install sd-metrics-lib
```

Optional extras for providers:

```bash
pip install sd-metrics-lib[jira]
pip install sd-metrics-lib[azure]
```

## Code examples

### Calculate amount of tickets developer resolves per day based on Jira ticket status change history.

This code should work on any project and give at least some data for analysis.

```python
from atlassian import Jira

from calculators import UserVelocityCalculator
from calculators.velocity_calculator import VelocityTimeUnit
from data_providers.jira.issue_provider import JiraIssueProvider
from data_providers.jira.worklog_extractor import JiraStatusChangeWorklogExtractor
from data_providers.story_point_extractor import ConstantStoryPointExtractor

JIRA_SERVER = 'server_url'
JIRA_LOGIN = 'login'
JIRA_PASS = 'password'
jira_client = Jira(JIRA_SERVER, JIRA_LOGIN, JIRA_PASS, cloud=True)

jql = " project in ('TBC') AND resolutiondate >= 2022-08-01 "
jql_issue_provider = JiraIssueProvider(jira_client, jql, additional_fields=['changelog'])

story_point_extractor = ConstantStoryPointExtractor()
jira_worklog_extractor = JiraStatusChangeWorklogExtractor(['In Progress', 'In Development'])

velocity_calculator = UserVelocityCalculator(issue_provider=jql_issue_provider,
                                             story_point_extractor=story_point_extractor,
                                             worklog_extractor=jira_worklog_extractor)
velocity = velocity_calculator.calculate(velocity_time_unit=VelocityTimeUnit.DAY)

print(velocity)
```

### Calculate amount of story points developer resolves per day based on Azure DevOps work items.

This example uses Azure DevOps WIQL to fetch closed items and derives time spent per user from status/assignment changes.
It also demonstrates enabling concurrency with a thread pool and caching results with a TTL cache.

```python
from cachetools import TTLCache
from concurrent.futures import ThreadPoolExecutor

from azure.devops.connection import Connection
from msrest.authentication import BasicAuthentication

from calculators import UserVelocityCalculator
from calculators.velocity_calculator import VelocityTimeUnit
from data_providers.azure.issue_provider import AzureIssueProvider
from data_providers.azure.story_point_extractor import AzureStoryPointExtractor
from data_providers.azure.worklog_extractor import AzureStatusChangeWorklogExtractor
from data_providers.issue_provider import CachingIssueProvider

# Caches and thread pools
JQL_CACHE = TTLCache(maxsize=100, ttl=600)
jira_fetch_executor = ThreadPoolExecutor(max_workers=100, thread_name_prefix="jira-fetch")

ORGANIZATION_URL = 'https://dev.azure.com/your_org'
PERSONAL_ACCESS_TOKEN = 'your_pat'

credentials = BasicAuthentication('', PERSONAL_ACCESS_TOKEN)
connection = Connection(base_url=ORGANIZATION_URL, creds=credentials)
wit_client = connection.clients.get_work_item_tracking_client()

wiql = """
       SELECT [System.Id]
       FROM workitems
       WHERE
           [System.TeamProject] = 'YourProject'
         AND [System.State] IN ('Closed', 'Done', 'Resolved')
         AND [System.WorkItemType] IN ('User Story', 'Bug')
         AND [Microsoft.VSTS.Common.ClosedDate] >= '2025-08-01'
       ORDER BY [System.ChangedDate] DESC \
       """

# Use thread pool and cache
issue_provider = AzureIssueProvider(wit_client, query=wiql, thread_pool_executor=jira_fetch_executor)
issue_provider = CachingIssueProvider(issue_provider, cache=JQL_CACHE)

story_point_extractor = AzureStoryPointExtractor(default_story_points_value=1)
worklog_extractor = AzureStatusChangeWorklogExtractor(transition_statuses=['In Progress'])

velocity_calculator = UserVelocityCalculator(issue_provider=issue_provider,
                                             story_point_extractor=story_point_extractor,
                                             worklog_extractor=worklog_extractor)
velocity = velocity_calculator.calculate(velocity_time_unit=VelocityTimeUnit.DAY)

print(velocity)
```

## Version history

### 2.0

+ (Feature) Add integration with Azure DevOps
+ (Feature) Add a generic CachingIssueProvider to wrap any IssueProvider and remove CachingJiraIssueProvider

### 1.2.1

+ **(Improvement)** Add possibility to adjust init time
+ **(Bug Fix)** Fix bug with wrong cache data fetching
+ **(Bug Fix)** Fix bug in week time period end date resolving

### 1.2

+ **(Feature)** Added BoundarySimpleWorkTimeExtractor
+ **(Improvement)** Filter unneeded changelog items for better performance
+ **(Improvement)** Add T-Shirt to story points mapping util class
+ **(Improvement)** Add helper enums
+ **(Bug Fix)** Fix bug with story points returned instead of spent time
+ **(Bug Fix)** Fix bug with missing time for active status
+ **(Bug Fix)** Fix bug with passing class instances in extractor

### 1.1.4

+ **(Improvement)** Add multithreading support for JiraIssueProvider.

### 1.1.3

+ **(Feature)** Add CachingJiraIssueProvider.

### 1.1.2

+ **(Improvement)** Add story points getter for GeneralizedTeamVelocityCalculator.

### 1.1.1

+ **(Improvement)** Execute data fetching in Jira velocity calculators only once.
+ **(Improvement)** Add story points getter for Jira velocity calculators.

### 1.1

+ **(Feature)** Add team velocity calculator.
+ **(Improvement)** Add JQL filter for last modified data.
+ **(Bug Fix)** Fix wrong user resolving in JiraStatusChangeWorklogExtractor.
+ **(Bug Fix)** Fix resolving more time than spent period of time.
+ **(Bug Fix)** Fix Jira filter query joins without AND.

### 1.0.3

+ **(Improvement)** Add JiraIssueSearchQueryBuilder util class.
+ **(Improvement)** Add TimeRangeGenerator util class.
+ **(Bug Fix)** Fix filtering by status when no status list passed.

### 1.0.2

+ **(Bug Fix)** Fix package import exception after installing from pypi.

### 1.0

+ **(Feature)** Add user velocity calculator.
