11import csv
22from decimal import Decimal , ROUND_HALF_UP
33import dataclasses
4- from datetime import datetime , timedelta
4+ from datetime import datetime , timedelta , timezone
55import logging
66import os
77
@@ -38,6 +38,8 @@ def get_rates():
3838@dataclasses .dataclass
3939class InvoiceRow :
4040 InvoiceMonth : str = ""
41+ Report_Start_Time : str = ""
42+ Report_End_Time : str = ""
4143 Project_Name : str = ""
4244 Project_ID : str = ""
4345 PI : str = ""
@@ -50,12 +52,15 @@ class InvoiceRow:
5052 Invoice_Type : str = ""
5153 Rate : Decimal = 0
5254 Cost : Decimal = 0
55+ Generated_At : str = ""
5356
5457 @classmethod
5558 def get_headers (cls ):
5659 """Returns all headers for display."""
5760 return [
5861 "Invoice Month" ,
62+ "Report Start Time" ,
63+ "Report End Time" ,
5964 "Project - Allocation" ,
6065 "Project - Allocation ID" ,
6166 "Manager (PI)" ,
@@ -68,6 +73,7 @@ def get_headers(cls):
6873 "SU Type" ,
6974 "Rate" ,
7075 "Cost" ,
76+ "Generated At" ,
7177 ]
7278
7379 def get_value (self , field : str ):
@@ -156,7 +162,7 @@ def default_end_argument():
156162 return pytz .utc .localize (d )
157163
158164 @staticmethod
159- def upload_to_s3 (s3_endpoint , s3_bucket , file_location , invoice_month ):
165+ def upload_to_s3 (s3_endpoint , s3_bucket , file_location , invoice_month , end_time ):
160166 s3_key_id = os .getenv ("S3_INVOICING_ACCESS_KEY_ID" )
161167 s3_secret = os .getenv ("S3_INVOICING_SECRET_ACCESS_KEY" )
162168
@@ -182,7 +188,18 @@ def upload_to_s3(s3_endpoint, s3_bucket, file_location, invoice_month):
182188 s3 .upload_file (file_location , Bucket = s3_bucket , Key = primary_location )
183189 logger .info (f"Uploaded to { primary_location } ." )
184190
185- timestamp = datetime .utcnow ().strftime ("%Y%m%dT%H%M%SZ" )
191+ # Upload daily copy
192+ # End time is exclusive, subtract one second to find the inclusive end date
193+ invoice_date = end_time - timedelta (seconds = 1 )
194+ invoice_date = invoice_date .strftime ("%Y-%m-%d" )
195+ daily_location = (
196+ f"Invoices/{ invoice_month } /Service Invoices/NERC Storage { invoice_date } .csv"
197+ )
198+ s3 .upload_file (file_location , Bucket = s3_bucket , Key = daily_location )
199+ logger .info (f"Uploaded to { daily_location } ." )
200+
201+ # Archival copy
202+ timestamp = datetime .now (tz = timezone .utc ).strftime ("%Y%m%dT%H%M%SZ" )
186203 secondary_location = (
187204 f"Invoices/{ invoice_month } /"
188205 f"Archive/NERC Storage { invoice_month } { timestamp } .csv"
@@ -191,6 +208,8 @@ def upload_to_s3(s3_endpoint, s3_bucket, file_location, invoice_month):
191208 logger .info (f"Uploaded to { secondary_location } ." )
192209
193210 def handle (self , * args , ** options ):
211+ generated_at = datetime .now (tz = timezone .utc ).isoformat (timespec = "seconds" )
212+
194213 def get_outages_for_service (cluster_name : str ):
195214 """Get outages for a service from nerc-rates.
196215
@@ -220,6 +239,8 @@ def process_invoice_row(allocation, attrs, su_name, rate):
220239 if time > 0 :
221240 row = InvoiceRow (
222241 InvoiceMonth = options ["invoice_month" ],
242+ Report_Start_Time = options ["start" ].isoformat (),
243+ Report_End_Time = options ["end" ].isoformat (),
223244 Project_Name = allocation .get_attribute (
224245 attributes .ALLOCATION_PROJECT_NAME
225246 ),
@@ -236,6 +257,7 @@ def process_invoice_row(allocation, attrs, su_name, rate):
236257 Invoice_Type = su_name ,
237258 Rate = rate ,
238259 Cost = (time * rate ).quantize (Decimal (".01" ), rounding = ROUND_HALF_UP ),
260+ Generated_At = generated_at ,
239261 )
240262 csv_invoice_writer .writerow (row .get_values ())
241263
@@ -331,4 +353,5 @@ def process_invoice_row(allocation, attrs, su_name, rate):
331353 options ["s3_bucket_name" ],
332354 options ["output" ],
333355 options ["invoice_month" ],
356+ options ["end" ],
334357 )
0 commit comments