Skip to content

Commit aeb25fa

Browse files
committed
Implemented processor for baremetal
1 parent b6ffd32 commit aeb25fa

10 files changed

Lines changed: 155 additions & 48 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from dataclasses import dataclass
2+
3+
4+
from process_report.invoices import invoice
5+
6+
7+
@dataclass
8+
class BMInvoice(invoice.Invoice):
9+
name: str = "bm_projects"
10+
export_columns_list = [
11+
invoice.INVOICE_DATE_FIELD,
12+
invoice.PROJECT_FIELD,
13+
invoice.PROJECT_ID_FIELD,
14+
invoice.PI_FIELD,
15+
invoice.INVOICE_EMAIL_FIELD,
16+
invoice.INVOICE_ADDRESS_FIELD,
17+
invoice.INSTITUTION_FIELD,
18+
invoice.INSTITUTION_ID_FIELD,
19+
invoice.SU_HOURS_FIELD,
20+
invoice.SU_TYPE_FIELD,
21+
invoice.RATE_FIELD,
22+
invoice.COST_FIELD,
23+
invoice.CREDIT_FIELD,
24+
invoice.CREDIT_CODE_FIELD,
25+
invoice.BALANCE_FIELD,
26+
]
27+
28+
def _prepare_export(self):
29+
self.export_data = self.data[self.data[invoice.CLUSTER_NAME_FIELD] == "bm"]

process_report/process_report.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from process_report import util
1111
from process_report.invoices import (
1212
invoice,
13+
bm_invoice,
1314
lenovo_invoice,
1415
nonbillable_invoice,
1516
billable_invoice,
@@ -96,6 +97,7 @@ def main():
9697
MOCA_prepaid_invoice.MOCAPrepaidInvoice,
9798
prepay_credits_snapshot.PrepayCreditsSnapshot,
9899
ocp_test_invoice.OcpTestInvoice,
100+
bm_invoice.BMInvoice,
99101
],
100102
invoice_settings.upload_to_s3,
101103
)

process_report/processors/bm_usage_processor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from dataclasses import dataclass
22

3-
import pandas
4-
53
from process_report.invoices import invoice
64
from process_report.processors import processor
75

86

97
@dataclass
108
class BMUsageProcessor(processor.Processor):
119
def _get_bm_project_mask(self):
12-
return pandas.Series(True, index=self.data.index) # TODO: Remove dummy mask
10+
return self.data[invoice.CLUSTER_NAME_FIELD] == "bm"
1311

1412
def _process(self):
1513
bm_projects_mask = self._get_bm_project_mask()

process_report/processors/bu_subsidy_processor.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@ def _get_subsidy_eligible_projects(data):
3434
filtered_data = filtered_data[
3535
filtered_data[invoice.INSTITUTION_FIELD] == "Boston University"
3636
]
37-
filtered_data = (
38-
filtered_data[ # TODO Does it make sense to test this filter in test cases?
39-
~(filtered_data[invoice.PROJECT_ID_FIELD] == "ESI Bare Metal")
40-
]
41-
)
37+
filtered_data = filtered_data[
38+
~(filtered_data[invoice.CLUSTER_NAME_FIELD] == "bm")
39+
]
4240

4341
return filtered_data
4442

process_report/processors/new_pi_credit_processor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def _filter_missing_pis(self, data):
102102
return data[~data["Missing PI"]]
103103

104104
def _filter_bm_projects(self, data):
105-
return data[~(data[invoice.PROJECT_ID_FIELD] == "ESI Bare Metal")]
105+
return data[~(data[invoice.CLUSTER_NAME_FIELD] == "bm")]
106106

107107
def _get_credit_eligible_projects(self, data: pandas.DataFrame):
108108
filtered_data = self._filter_nonbillables(data)

process_report/tests/e2e/test_e2e_pipeline.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"MOCA-A_Prepaid_Groups-2025-06-Invoice.csv",
2222
"NERC_Prepaid_Group-Credits-2025-06.csv",
2323
"OCP_TEST 2025-06.csv",
24+
"bm_projects 2025-06.csv",
2425
]
2526

2627
EXPECTED_DIRECTORIES = ["pi_invoices"]

process_report/tests/unit/processors/test_bm_usage_processor.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,26 @@
66

77

88
class TestBUSubsidyProcessor(TestCase):
9-
def test_get_bm_project_mask(self):
10-
test_invoice = pandas.DataFrame({})
11-
12-
answer_invoice = test_invoice.iloc[[0, 2]]
13-
14-
bm_usage_proc = test_utils.new_bm_usage_processor(data=test_invoice)
15-
bm_project_mask = bm_usage_proc._get_bm_project_mask()
16-
self.assertTrue(test_invoice[bm_project_mask].equals(answer_invoice))
17-
189
def test_process_bm_usage(self):
1910
test_invoice = pandas.DataFrame(
2011
{
21-
"Project - Allocation": ["test", "test bm-bm"],
22-
"Project - Allocation ID": [None] * 2,
23-
"Invoice Email": [None] * 2,
12+
"Project - Allocation": ["test", "test bm-bm", "not-bm"],
13+
"Project - Allocation ID": [None] * 3,
14+
"Invoice Email": [None] * 3,
15+
"Cluster Name": ["bm", "bm", "ocp"],
2416
}
2517
)
2618

2719
answer_invoice = pandas.DataFrame(
2820
{
29-
"Project - Allocation": ["test BM Usage", "test bm-bm BM Usage"],
30-
"Project - Allocation ID": ["ESI Bare Metal"] * 2,
31-
"Invoice Email": ["nclinton@bu.edu"] * 2,
21+
"Project - Allocation": [
22+
"test BM Usage",
23+
"test bm-bm BM Usage",
24+
"not-bm",
25+
],
26+
"Project - Allocation ID": ["ESI Bare Metal"] * 2 + [None],
27+
"Invoice Email": ["nclinton@bu.edu"] * 2 + [None],
28+
"Cluster Name": ["bm", "bm", "ocp"],
3229
}
3330
)
3431

process_report/tests/unit/processors/test_bu_subsidy_processor.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def _get_test_invoice(
3232
institution=None,
3333
is_billable=None,
3434
missing_pi=None,
35+
clusters=None,
3536
):
3637
if not balances:
3738
balances = pi_balances
@@ -48,6 +49,9 @@ def _get_test_invoice(
4849
if not missing_pi:
4950
missing_pi = [False for _ in range(len(pi))]
5051

52+
if not clusters:
53+
clusters = ["" for _ in range(len(pi))]
54+
5155
return pandas.DataFrame(
5256
{
5357
"Manager (PI)": pi,
@@ -57,6 +61,7 @@ def _get_test_invoice(
5761
"Institution": institution,
5862
"Is Billable": is_billable,
5963
"Missing PI": missing_pi,
64+
"Cluster Name": clusters,
6065
}
6166
)
6267

@@ -175,3 +180,21 @@ def test_two_pi(self):
175180
answer_invoice["PI Balance"] = [0, 60, 0, 0]
176181

177182
self._assert_result_invoice(subsidy_amount, test_invoice, answer_invoice)
183+
184+
def test_exclude_bm_cluster(self):
185+
"""Projects in the 'bm' cluster should be excluded from BU subsidy calculation."""
186+
subsidy_amount = 100
187+
test_invoice = self._get_test_invoice(
188+
["PI"] * 2, # single PI (will be broadcast to two rows by lengths below)
189+
pi_balances=[60, 60],
190+
project_names=["P1", "P2"],
191+
clusters=["bm", "ocp"],
192+
)
193+
194+
answer_invoice = test_invoice.copy()
195+
answer_invoice["Project"] = answer_invoice["Project - Allocation"]
196+
# bm allocation gets no subsidy, non-bm allocation gets up to its PI balance (60)
197+
answer_invoice["Subsidy"] = [0, 60]
198+
answer_invoice["PI Balance"] = [60, 0]
199+
200+
self._assert_result_invoice(subsidy_amount, test_invoice, answer_invoice)

0 commit comments

Comments
 (0)