Skip to content

Commit

Permalink
Merge pull request #6 from rogers-obrien-rad/feature/3/scenarios
Browse files Browse the repository at this point in the history
Feature/3/scenarios
  • Loading branch information
HagenFritz authored May 21, 2024
2 parents aadf48c + 7758f88 commit 2f315e7
Show file tree
Hide file tree
Showing 13 changed files with 714 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,6 @@ replay_pid*

# VS Code
*.code-workspace

# Directories
*reference*
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
requests
pytest
python-dotenv
python-dotenv
matplotlib
2 changes: 1 addition & 1 deletion smartpm/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from smartpm.exceptions import SmartPMError, AuthenticationError, NotFoundError, RateLimitExceededError, BadRequestError, NoCommentsFoundError

class SmartPMClient:
BASE_URL = 'https://live.smartpmtech.com/public/v1'
BASE_URL = 'https://live.smartpmtech.com/public'

def __init__(self, api_key, company_id):
self.api_key = api_key
Expand Down
19 changes: 19 additions & 0 deletions smartpm/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import functools

def api_wrapper(func):
"""Decorator to mark API wrapper functions."""
@functools.wraps(func)
def wrapper_api_wrapper(*args, **kwargs):
# You can add common functionality here, such as logging
print(f"Calling API wrapper function: {func.__name__}")
return func(*args, **kwargs)
return wrapper_api_wrapper

def utility(func):
"""Decorator to mark utility functions."""
@functools.wraps(func)
def wrapper_utility(*args, **kwargs):
# You can add common functionality here, such as logging
print(f"Calling utility function: {func.__name__}")
return func(*args, **kwargs)
return wrapper_utility
27 changes: 18 additions & 9 deletions smartpm/endpoints/projects.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from smartpm.client import SmartPMClient

from smartpm.decorators import api_wrapper
from smartpm.logging_config import logger
class Projects:
def __init__(self, client: SmartPMClient):
self.client = client

@api_wrapper
def get_projects(self, as_of=None):
"""
Access basic project data: https://developers.smartpmtech.com/#operation/get-projects
Expand All @@ -19,12 +21,16 @@ def get_projects(self, as_of=None):
<response.json> : list of dict
projects data as a JSON object
"""
logger.debug(f"Fetching projects as of: {as_of}")
params = {}
if as_of:
params['asOf'] = as_of

return self.client._get(endpoint='projects', params=params)

endpoint = 'v1/projects'
response = self.client._get(endpoint=endpoint, params=params)
return response

@api_wrapper
def get_project(self, project_id):
"""
Get a specific project by its ID: https://developers.smartpmtech.com/#operation/get-project
Expand All @@ -39,10 +45,12 @@ def get_project(self, project_id):
<response.json> : dict
project details as a JSON object
"""
endpoint = f'projects/{project_id}'
logger.debug(f"Fetching project with ID: {project_id}")
endpoint = f'v1/projects/{project_id}'
response = self.client._get(endpoint=endpoint)
return response

return self.client._get(endpoint)

@api_wrapper
def get_project_comments(self, project_id):
"""
Get comments for a specific project: https://developers.smartpmtech.com/#operation/get-project-comments
Expand All @@ -57,6 +65,7 @@ def get_project_comments(self, project_id):
<response.json> : list of dict
project comments as a JSON object
"""
endpoint = f'projects/{project_id}/comments'

return self.client._get(endpoint)
logger.debug(f"Fetching comments for project ID: {project_id}")
endpoint = f'v1/projects/{project_id}/comments'
response = self.client._get(endpoint=endpoint)
return response
146 changes: 146 additions & 0 deletions smartpm/endpoints/scenarios.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
from smartpm.client import SmartPMClient
from smartpm.utils import plot_percent_complete_curve, plot_earned_schedule_curve
from smartpm.decorators import api_wrapper, utility
from smartpm.logging_config import logger

class Scenarios:
def __init__(self, client: SmartPMClient):
self.client = client

@api_wrapper
def get_scenarios(self, project_id, as_of=None):
"""
Get scenarios for a specific project: https://developers.smartpmtech.com/#operation/get-scenarios
Parameters
----------
project_id : int
ID of the project to retrieve scenarios for
as_of : str, default None
Return scenarios that have changed since date in format `2023-07-19T12:00:00`
Returns
-------
<response.json> : list of dict
project scenarios as a JSON object
"""
logger.debug(f"Fetching scenarios for project_id: {project_id}, as_of: {as_of}")
params = {}
if as_of:
params['asOf'] = as_of

endpoint = f'v1/projects/{project_id}/scenarios'

return self.client._get(endpoint=endpoint, params=params)

@api_wrapper
def get_scenario_details(self, project_id, scenario_id, data_date=None):
"""
Get the details for a specific scenario: https://developers.smartpmtech.com/#operation/get-scenario-details
Parameters
----------
project_id : int
ID of the project containing the scenario
scenario_id : int
ID of the scenario to retrieve details for
data_date : str, default None
Data date in format `yyyy-MM-dd` for which to retrieve the scenario details
If None, will use the latest data date
Returns
------
<response.json> : dict
scenario details as a JSON object
"""
logger.debug(f"Fetching scenario details for project_id: {project_id}, scenario_id: {scenario_id}, data_date: {data_date}")
params = {}
if data_date:
params['dataDate'] = data_date

endpoint = f'v1/projects/{project_id}/scenarios/{scenario_id}'
return self.client._get(endpoint=endpoint, params=params)

@api_wrapper
def get_percent_complete_curve(self, project_id, scenario_id, delta=False):
"""
Get Percent Complete Curve data for a specific scenario: https://developers.smartpmtech.com/#operation/get-percent-complete-curve
Parameters
----------
project_id : str
ID of the project containing the scenario
scenario_id : str
ID of the scenario to retrieve the percent complete curve for
delta : bool, default False
Return the change of progress between periods if True
Returns
-------
<response.json> : dict
percent complete curve data as a JSON object
"""
logger.debug(f"Fetching percent complete curve data for project_id: {project_id}, scenario_id: {scenario_id}, delta: {delta}")
params = {'delta': str(delta).lower()}

endpoint = f'v2/projects/{project_id}/scenarios/{scenario_id}/percent-complete-curve'
return self.client._get(endpoint=endpoint, params=params)

@utility
def plot_percent_complete_curve(self, project_id, scenario_id, delta=False):
"""
Retrieve the percent complete curve and plot the progress.
Reproduces this figure from SmartPM: https://help.smartpmtech.com/the-progress-curve
Parameters
----------
project_id : str
ID of the project containing the scenario
scenario_id : str
ID of the scenario to retrieve the percent complete curve for
delta : bool, default False
Return the change of progress between periods if True
"""
logger.debug(f"Plotting scenario progress for project_id: {project_id}, scenario_id: {scenario_id}, delta: {delta}")
curve_data = self.get_percent_complete_curve(project_id, scenario_id, delta)
plot_percent_complete_curve(curve_data)

@api_wrapper
def get_earned_schedule_curve(self, project_id, scenario_id):
"""
Get Earned Schedule Curve for a specific scenario.
Parameters
----------
project_id : str
ID of the project containing the scenario
scenario_id : str
ID of the scenario to retrieve the earned schedule curve for
Returns
-------
<response.json> : dict
earned schedule curve data as a JSON object
"""
logger.debug(f"Fetching earned schedule curve for project_id: {project_id}, scenario_id: {scenario_id}")
endpoint = f'v1/projects/{project_id}/scenarios/{scenario_id}/earned-schedule-curve'
response = self.client._get(endpoint=endpoint)

return response

@utility
def plot_earned_schedule_curve(self, project_id, scenario_id):
"""
Retrieve the earned days curve and plot the results
Reproduces this figure: https://help.smartpmtech.com/earned-baseline-days
Parameters
----------
project_id : str
ID of the project containing the scenario
scenario_id : str
ID of the scenario to retrieve the percent complete curve for
"""
logger.debug(f"Plotting earned schedule curve for project_id: {project_id}, scenario_id: {scenario_id}")
earned_days_data = self.get_earned_schedule_curve(project_id, scenario_id)
plot_earned_schedule_curve(earned_days_data)
16 changes: 16 additions & 0 deletions smartpm/logging_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import logging

# Create a logger
logger = logging.getLogger('smartpm')
logger.setLevel(logging.DEBUG)

# Create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# Create formatter and add it to the handler
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)

# Add the handler to the logger
logger.addHandler(ch)
Loading

0 comments on commit 2f315e7

Please sign in to comment.