Skip to content

Commit 0d07205

Browse files
committed
Allow limiting New-PI credit to partner institutions
`process_report.py` will now use `nerc_rates` to determine if the New-PI credit is limited to institutions that are MGHPCC partners for the given invoice month. The script determines whether an institution's MGHPCC partnership is active by first seeing if the `mghpcc_partnership_start_date` field is set in `institute_list.yaml`, then checks if the start date is within or before the invoice month.
1 parent 5f13e94 commit 0d07205

File tree

5 files changed

+75
-6
lines changed

5 files changed

+75
-6
lines changed

Diff for: process_report/invoices/billable_invoice.py

+32-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class BillableInvoice(discount_invoice.DiscountInvoice):
2323
nonbillable_pis: list[str]
2424
nonbillable_projects: list[str]
2525
old_pi_filepath: str
26+
limit_new_pi_credit_to_partners: bool = False
2627

2728
@staticmethod
2829
def _load_old_pis(old_pi_filepath) -> pandas.DataFrame:
@@ -115,6 +116,31 @@ def export_s3(self, s3_bucket):
115116
super().export_s3(s3_bucket)
116117
s3_bucket.upload_file(self.old_pi_filepath, self.PI_S3_FILEPATH)
117118

119+
def _filter_partners(self, data):
120+
active_partnerships = list()
121+
institute_list = util.load_institute_list()
122+
for institute_info in institute_list:
123+
if partnership_start_date := institute_info.get(
124+
"mghpcc_partnership_start_date", None
125+
):
126+
if (
127+
util.get_month_diff(self.invoice_month, partnership_start_date[:-3])
128+
>= 0
129+
):
130+
active_partnerships.append(institute_info["display_name"])
131+
132+
return data[data[invoice.INSTITUTION_FIELD].isin(active_partnerships)]
133+
134+
def _filter_excluded_su_types(self, data):
135+
return data[~(data[invoice.SU_TYPE_FIELD].isin(self.EXCLUDE_SU_TYPES))]
136+
137+
def _get_credit_eligible_projects(self, data: pandas.DataFrame):
138+
filtered_data = self._filter_excluded_su_types(data)
139+
if self.limit_new_pi_credit_to_partners:
140+
filtered_data = self._filter_partners(filtered_data)
141+
142+
return filtered_data
143+
118144
def _apply_credits_new_pi(
119145
self, data: pandas.DataFrame, old_pi_df: pandas.DataFrame
120146
):
@@ -140,19 +166,19 @@ def get_initial_credit_amount(
140166
)
141167
print(f"New PI Credit set at {new_pi_credit_amount} for {self.invoice_month}")
142168

143-
current_pi_set = set(data[invoice.PI_FIELD])
169+
credit_eligible_projects = self._get_credit_eligible_projects(data)
170+
current_pi_set = set(credit_eligible_projects[invoice.PI_FIELD])
144171
for pi in current_pi_set:
145-
credit_eligible_projects = data[
146-
(data[invoice.PI_FIELD] == pi)
147-
& ~(data[invoice.SU_TYPE_FIELD].isin(self.EXCLUDE_SU_TYPES))
172+
pi_projects = credit_eligible_projects[
173+
credit_eligible_projects[invoice.PI_FIELD] == pi
148174
]
149175
pi_age = self._get_pi_age(old_pi_df, pi, self.invoice_month)
150176
pi_old_pi_entry = old_pi_df.loc[
151177
old_pi_df[invoice.PI_PI_FIELD] == pi
152178
].squeeze()
153179

154180
if pi_age > 1:
155-
for i, row in credit_eligible_projects.iterrows():
181+
for i, row in pi_projects.iterrows():
156182
data.at[i, invoice.BALANCE_FIELD] = row[invoice.COST_FIELD]
157183
else:
158184
if pi_age == 0:
@@ -180,7 +206,7 @@ def get_initial_credit_amount(
180206

181207
credits_used = self.apply_flat_discount(
182208
data,
183-
credit_eligible_projects,
209+
pi_projects,
184210
remaining_credit,
185211
invoice.CREDIT_FIELD,
186212
invoice.BALANCE_FIELD,

Diff for: process_report/process_report.py

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pandas
77
import pyarrow
8+
from nerc_rates import load_from_url
89

910
from process_report import util
1011
from process_report.invoices import (
@@ -215,13 +216,17 @@ def main():
215216
if args.upload_to_s3:
216217
backup_to_s3_old_pi_file(old_pi_file)
217218

219+
rates_info = load_from_url()
218220
billable_inv = billable_invoice.BillableInvoice(
219221
name=args.output_file,
220222
invoice_month=invoice_month,
221223
data=merged_dataframe.copy(),
222224
nonbillable_pis=pi,
223225
nonbillable_projects=projects,
224226
old_pi_filepath=old_pi_file,
227+
limit_new_pi_credit_to_partners=rates_info.get_value_at(
228+
"Limit New PI Credit to MGHPCC Partners", invoice_month
229+
),
225230
)
226231

227232
util.process_and_export_invoices(

Diff for: process_report/tests/unit_tests.py

+35
Original file line numberDiff line numberDiff line change
@@ -818,3 +818,38 @@ def test_remove_prefix(self, mock_get_time, mock_get_bucket):
818818
process_report.upload_to_s3(filenames, invoice_month)
819819
for i, call_args in enumerate(mock_bucket.upload_file.call_args_list):
820820
self.assertTrue(answers[i] in call_args)
821+
822+
823+
class TestNERCRates(TestCase):
824+
@mock.patch("process_report.util.load_institute_list")
825+
def test_flag_limit_new_pi_credit(self, mock_load_institute_list):
826+
mock_load_institute_list.return_value = [
827+
{"display_name": "BU", "mghpcc_partnership_start_date": "2024-02-01"},
828+
{"display_name": "HU", "mghpcc_partnership_start_date": "2024-06-31"},
829+
{"display_name": "NEU", "mghpcc_partnership_start_date": "2024-11-01"},
830+
]
831+
sample_df = pandas.DataFrame(
832+
{
833+
"Institution": ["BU", "HU", "NEU", "MIT", "BC"],
834+
}
835+
)
836+
sample_inv = test_utils.new_billable_invoice(
837+
limit_new_pi_credit_to_partners=True
838+
)
839+
840+
# When no partnerships are active
841+
sample_inv.invoice_month = "2024-01"
842+
output_df = sample_inv._filter_partners(sample_df)
843+
self.assertTrue(output_df.empty)
844+
845+
# When some partnerships are active
846+
sample_inv.invoice_month = "2024-06"
847+
output_df = sample_inv._filter_partners(sample_df)
848+
answer_df = pandas.DataFrame({"Institution": ["BU", "HU"]})
849+
self.assertTrue(output_df.equals(answer_df))
850+
851+
# When all partnerships are active
852+
sample_inv.invoice_month = "2024-12"
853+
output_df = sample_inv._filter_partners(sample_df)
854+
answer_df = pandas.DataFrame({"Institution": ["BU", "HU", "NEU"]})
855+
self.assertTrue(output_df.equals(answer_df))

Diff for: process_report/tests/util.py

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def new_billable_invoice(
1010
nonbillable_pis=[],
1111
nonbillable_projects=[],
1212
old_pi_filepath="",
13+
limit_new_pi_credit_to_partners=False,
1314
):
1415
return billable_invoice.BillableInvoice(
1516
name,
@@ -18,6 +19,7 @@ def new_billable_invoice(
1819
nonbillable_pis,
1920
nonbillable_projects,
2021
old_pi_filepath,
22+
limit_new_pi_credit_to_partners,
2123
)
2224

2325

Diff for: requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
git+https://github.com/CCI-MOC/nerc-rates@74eb4a7#egg=nerc_rates
12
pandas
23
pyarrow
34
boto3

0 commit comments

Comments
 (0)