Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions process_report/invoices/bm_invoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from dataclasses import dataclass


from process_report.invoices import invoice


@dataclass
class BMInvoice(invoice.Invoice):
export_columns_list = [
invoice.INVOICE_DATE_FIELD,
invoice.PROJECT_FIELD,
invoice.PROJECT_ID_FIELD,
invoice.PI_FIELD,
invoice.INVOICE_EMAIL_FIELD,
invoice.INVOICE_ADDRESS_FIELD,
invoice.INSTITUTION_FIELD,
invoice.INSTITUTION_ID_FIELD,
invoice.SU_HOURS_FIELD,
invoice.SU_TYPE_FIELD,
invoice.RATE_FIELD,
invoice.COST_FIELD,
invoice.CREDIT_FIELD,
invoice.CREDIT_CODE_FIELD,
invoice.BALANCE_FIELD,
]

def _prepare_export(self):
self.export_data = self.data[
self.data[invoice.PROJECT_ID_FIELD] == "ESI Bare Metal"
]
22 changes: 21 additions & 1 deletion process_report/process_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from process_report import util
from process_report.invoices import (
bm_invoice,
lenovo_invoice,
nonbillable_invoice,
billable_invoice,
Expand All @@ -25,6 +26,7 @@
new_pi_credit_processor,
bu_subsidy_processor,
prepayment_processor,
bm_usage_processor,
)

### PI file field names
Expand Down Expand Up @@ -189,6 +191,12 @@ def main():
default="Lenovo",
help="Name of output csv for Lenovo SU Types invoice",
)
parser.add_argument(
"--bm-usage-file",
required=False,
default="bm_usage",
help="Name of output csv for Lenovo SU Types invoice",
)
parser.add_argument(
"--old-pi-file",
required=False,
Expand Down Expand Up @@ -278,10 +286,17 @@ def main():
)
validate_billable_pi_proc.process()

bm_usage_proc = bm_usage_processor.BMUsageProcessor(
"", invoice_month, validate_billable_pi_proc.data
)
bm_usage_proc.process()

### Credits and discounts processing

new_pi_credit_proc = new_pi_credit_processor.NewPICreditProcessor(
"",
invoice_month,
data=validate_billable_pi_proc.data,
data=bm_usage_proc.data,
old_pi_filepath=old_pi_file,
initial_credit_amount=new_pi_credit_amount,
limit_new_pi_credit_to_partners=(
Expand Down Expand Up @@ -358,6 +373,10 @@ def main():
name="", invoice_month=invoice_month, data=processed_data.copy()
)

bm_inv = bm_invoice.BMInvoice(
name=args.bm_usage_file, invoice_month=invoice_month, data=processed_data
)

util.process_and_export_invoices(
[
lenovo_inv,
Expand All @@ -367,6 +386,7 @@ def main():
bu_internal_inv,
pi_inv,
moca_prepaid_inv,
bm_inv,
],
args.upload_to_s3,
)
Expand Down
20 changes: 20 additions & 0 deletions process_report/processors/bm_usage_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from dataclasses import dataclass

import pandas

from process_report.invoices import invoice
from process_report.processors import processor


@dataclass
class BMUsageProcessor(processor.Processor):
def _get_bm_project_mask(self):
return pandas.Series(True, index=self.data.index) # TODO: Remove dummy mask

def _process(self):
bm_projects_mask = self._get_bm_project_mask()
self.data.loc[bm_projects_mask, invoice.PROJECT_FIELD] = self.data.loc[
bm_projects_mask, invoice.PROJECT_FIELD
].apply(lambda v: v + " BM Usage")
self.data.loc[bm_projects_mask, invoice.PROJECT_ID_FIELD] = "ESI Bare Metal"
self.data.loc[bm_projects_mask, invoice.INVOICE_EMAIL_FIELD] = "[email protected]"
7 changes: 6 additions & 1 deletion process_report/processors/bu_subsidy_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ def _get_subsidy_eligible_projects(data):
]
filtered_data = filtered_data[
filtered_data[invoice.INSTITUTION_FIELD] == "Boston University"
].copy()
]
filtered_data = (
filtered_data[ # TODO Does it make sense to test this filter in test cases?
Copy link
Contributor Author

@QuanMPhm QuanMPhm Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to test this filter in test cases for this processor class?

~(filtered_data[invoice.PROJECT_ID_FIELD] == "ESI Bare Metal")
]
)

return filtered_data

Expand Down
4 changes: 4 additions & 0 deletions process_report/processors/new_pi_credit_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,14 @@ def _filter_nonbillables(self, data):
def _filter_missing_pis(self, data):
return data[~data["Missing PI"]]

def _filter_bm_projects(self, data):
return data[~(data[invoice.PROJECT_ID_FIELD] == "ESI Bare Metal")]

def _get_credit_eligible_projects(self, data: pandas.DataFrame):
filtered_data = self._filter_nonbillables(data)
filtered_data = self._filter_missing_pis(filtered_data)
filtered_data = self._filter_excluded_su_types(filtered_data)
filtered_data = self._filter_bm_projects(filtered_data)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^

if self.limit_new_pi_credit_to_partners:
filtered_data = self._filter_partners(filtered_data)

Expand Down
37 changes: 37 additions & 0 deletions process_report/tests/unit/processors/test_bm_usage_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from unittest import TestCase

import pandas

from process_report.tests import util as test_utils


class TestBUSubsidyProcessor(TestCase):
def test_get_bm_project_mask(self):
test_invoice = pandas.DataFrame({})

answer_invoice = test_invoice.iloc[[0, 2]]

bm_usage_proc = test_utils.new_bm_usage_processor(data=test_invoice)
bm_project_mask = bm_usage_proc._get_bm_project_mask()
self.assertTrue(test_invoice[bm_project_mask].equals(answer_invoice))

def test_process_bm_usage(self):
test_invoice = pandas.DataFrame(
{
"Project - Allocation": ["test", "test bm-bm"],
"Project - Allocation ID": [None] * 2,
"Invoice Email": [None] * 2,
}
)

answer_invoice = pandas.DataFrame(
{
"Project - Allocation": ["test BM Usage", "test bm-bm BM Usage"],
"Project - Allocation ID": ["ESI Bare Metal"] * 2,
"Invoice Email": ["[email protected]"] * 2,
}
)

bm_usage_proc = test_utils.new_bm_usage_processor(data=test_invoice)
bm_usage_proc.process()
self.assertTrue(bm_usage_proc.data.equals(answer_invoice))
15 changes: 15 additions & 0 deletions process_report/tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
new_pi_credit_processor,
bu_subsidy_processor,
prepayment_processor,
bm_usage_processor,
)


Expand Down Expand Up @@ -175,3 +176,17 @@ def new_prepayment_processor(
prepay_debits_filepath,
upload_to_s3,
)


def new_bm_usage_processor(
name="",
invoice_month="0000-00",
data=None,
):
if data is None:
data = pandas.DataFrame()
return bm_usage_processor.BMUsageProcessor(
name,
invoice_month,
data,
)
Loading