Skip to content

Commit 6b9bb0e

Browse files
committed
Allow supplemental API data to be passed to ColdfrontFetchProcessor
This allows passing data for billable projects not currently in Coldfront Namely bare metal projects Added unit test and updated e2e test
1 parent 72b6e76 commit 6b9bb0e

9 files changed

Lines changed: 80 additions & 2 deletions

File tree

process_report/loader.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,5 +208,13 @@ def get_nonbillable_timed_projects(self) -> list[tuple[str, str]]:
208208
].itertuples(index=False, name=None)
209209
)
210210

211+
def get_supplement_api_data(self) -> list[dict]:
212+
supplemental_data = []
213+
if invoice_settings.supplement_api_data_filepath:
214+
with open(invoice_settings.supplement_api_data_filepath) as f:
215+
supplemental_data = yaml.safe_load(f)
216+
217+
return supplemental_data
218+
211219

212220
loader = Loader()

process_report/processors/coldfront_fetch_processor.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class ColdfrontFetchProcessor(processor.Processor):
3333
default_factory=loader.get_nonbillable_projects
3434
)
3535
coldfront_data_filepath: str = invoice_settings.coldfront_api_filepath
36+
supplement_api_data: list[dict] = field(
37+
default_factory=lambda: loader.get_supplement_api_data()
38+
)
3639

3740
@functools.cached_property
3841
def coldfront_client(self):
@@ -96,7 +99,8 @@ def _get_coldfront_api_data(self):
9699
def _get_allocation_data(self, coldfront_api_data):
97100
"""Returns a mapping of (project ID, cluster name) tupels to a dict of project name, PI name, and institution code."""
98101
allocation_data = {}
99-
for project_dict in coldfront_api_data:
102+
combined_api_data = coldfront_api_data + self.supplement_api_data
103+
for project_dict in combined_api_data:
100104
try:
101105
# Allow allocation to not have institute code
102106
project_id = project_dict["attributes"][CF_ATTR_ALLOCATED_PROJECT_ID]

process_report/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
class Settings(BaseSettings):
99
# Coldfront info
1010
coldfront_api_filepath: str | None = None
11+
supplement_api_data_filepath: str | None = None
1112
keycloak_client_id: str | None = None
1213
keycloak_client_secret: str | None = None
1314

process_report/tests/e2e/test_data/test_PI.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
PI,First Invoice Month,Initial Credits,1st Month Used,2nd Month Used
2+
pi4@harvard.edu,2025-06,1000,400.00,0
23
pi5@harvard.edu,2025-06,0.00,0.00,0.00
34
pi6@mit.edu,2025-06,0.00,0.00,0.00
45
pi4@example.edu,2025-06,0.00,0.00,0.00

process_report/tests/e2e/test_data/test_invoices/test_NERC OpenShift 2025-04.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ Invoice Month,Project - Allocation,Project - Allocation ID,Manager (PI),Cluster
33
2024-01,P1ID,P1ID,,shift,,,,,280,OpenStack GPUA100SXM4,0.013,100
44
2024-01,P2ID,P2ID,,shift,,,,,280,OpenShift CPU,0.013,200
55
2024-01,P3ID,P3ID,,shift,,,,,280,OpenShift CPU,0.013,300
6+
2024-01,P4-supplement,P4-supplement,,shift,,,,,300,OpenShift CPU,0.013,400
67
2024-01,P9ID,P9ID,,shift,,,,,280,OpenShift CPU,0.013,3000
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- project:
2+
pi: pi4@harvard.edu
3+
attributes:
4+
Allocated Project ID: P4-supplement
5+
Allocated Project Name: P4-supplement-name
6+
resource:
7+
name: shift

process_report/tests/e2e/test_e2e_pipeline.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ def _prepare_pipeline_execution(
113113
env = os.environ.copy()
114114
env["INVOICE_MONTH"] = INVOICE_MONTH
115115
env["COLDFRONT_API_FILEPATH"] = str(test_files["test_coldfront_api_data.json"])
116+
env["SUPPLEMENT_API_DATA_FILEPATH"] = str(
117+
test_files["test_supplement_api_data.yaml"]
118+
)
116119
env["FETCH_FROM_S3"] = "false"
117120
env["UPLOAD_TO_S3"] = "false"
118121
env["invoice_path_template"] = str(test_files["test_invoice_dir"])

process_report/tests/unit/processors/test_coldfront_fetch_processor.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,48 @@ def test_missing_project_cluster_tuples(self, mock_get_allocation_data):
259259
assert str(cm.value) == (
260260
f"Projects {expected_missing} not found in Coldfront and are billable! Please check the project names"
261261
)
262+
263+
@mock.patch(
264+
"process_report.processors.coldfront_fetch_processor.ColdfrontFetchProcessor._fetch_coldfront_allocation_api",
265+
)
266+
def test_supplement_api_data_used_when_coldfront_missing(
267+
self, mock_get_allocation_data
268+
):
269+
"""Supplement API rows are applied to invoice in _get_allocation_data()."""
270+
mock_get_allocation_data.return_value = self._get_mock_allocation_data(
271+
["P1"],
272+
["PI1"],
273+
["IC1"],
274+
["stack"],
275+
)
276+
277+
# Supplemental data should follow same structure as Coldfront data,
278+
# only missing "Is Course?" and "Institution-Specific Code" fields
279+
supplemental_df = self._get_mock_allocation_data(
280+
["P2"],
281+
["PI2"],
282+
["Delete Institude Code"],
283+
["stack"],
284+
)
285+
del supplemental_df[0]["attributes"]["Institution-Specific Code"]
286+
287+
test_invoice = self._get_test_invoice(
288+
["P1", "P2"], cluster_name=["stack", "stack"]
289+
)
290+
291+
expected_invoice = self._get_test_invoice(
292+
["P1", "P2"],
293+
["P1-name", "P2-name"],
294+
["PI1", "PI2"],
295+
["IC1", "N/A"],
296+
["stack", "stack"],
297+
[False, False],
298+
)
299+
300+
test_coldfront_fetch_proc = test_utils.new_coldfront_fetch_processor(
301+
data=test_invoice, supplement_api_data=supplemental_df
302+
)
303+
test_coldfront_fetch_proc.process()
304+
output_invoice = test_coldfront_fetch_proc.data
305+
306+
assert output_invoice.equals(expected_invoice)

process_report/tests/util.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,23 @@ def new_coldfront_fetch_processor(
6363
data=None,
6464
nonbillable_projects=None,
6565
coldfront_data_filepath=None,
66+
supplement_api_data=None,
6667
):
6768
if data is None:
6869
data = pandas.DataFrame()
6970
if nonbillable_projects is None:
7071
nonbillable_projects = pandas.DataFrame(
7172
columns=["Project Name", "Cluster", "Is Timed", "Is Billable Override"]
7273
)
74+
if supplement_api_data is None:
75+
supplement_api_data = []
7376
return coldfront_fetch_processor.ColdfrontFetchProcessor(
74-
invoice_month, data, name, nonbillable_projects, coldfront_data_filepath
77+
invoice_month,
78+
data,
79+
name,
80+
nonbillable_projects,
81+
coldfront_data_filepath,
82+
supplement_api_data,
7583
)
7684

7785

0 commit comments

Comments
 (0)