diff --git a/local-app/python-tools/billing/aws_monthly_cost.py b/local-app/python-tools/billing/aws_monthly_cost.py new file mode 100755 index 00000000..6d80a32a --- /dev/null +++ b/local-app/python-tools/billing/aws_monthly_cost.py @@ -0,0 +1,80 @@ +#!/bin/env python + +import boto3 +import csv +import datetime +from botocore.exceptions import ClientError + +def get_monthly_range(target_date=None): + """Calculates the start of the current month and the start of the next month.""" + if target_date is None: + target_date = datetime.date.today() + + # Start of target month + start = target_date.replace(day=1) + + # Start of next month (End date is exclusive in AWS API) + if start.month == 12: + end = start.replace(year=start.year + 1, month=1) + else: + end = start.replace(month=start.month + 1) + + return start.strftime('%Y-%m-%d'), end.strftime('%Y-%m-%d') + +def fetch_aws_costs(): + client = boto3.client('ce', region_name='us-east-1') # CE API is us-east-1 only + start_date, end_date = get_monthly_range() + + print(f"Fetching costs from {start_date} to {end_date}...") + + try: + response = client.get_cost_and_usage( + TimePeriod={'Start': start_date, 'End': end_date}, + Granularity='MONTHLY', + Metrics=['BlendedCost', 'UnblendedCost', 'AmortizedCost', 'UsageQuantity'], + GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}] + ) + + rows = [] + grand_totals = {'blended': 0.0, 'unblended': 0.0, 'amortized': 0.0} + + for group in response['ResultsByTime'][0]['Groups']: + service_name = group['Keys'][0] + metrics = group['Metrics'] + + # Extracting values + usage_qty = float(metrics['UsageQuantity']['Amount']) + usage_unit = metrics['UsageQuantity']['Unit'] + blended = float(metrics['BlendedCost']['Amount']) + unblended = float(metrics['UnblendedCost']['Amount']) + amortized = float(metrics['AmortizedCost']['Amount']) + + rows.append([service_name, f"{usage_qty:.2f}", usage_unit, f"{blended:.2f}", f"{unblended:.2f}", f"{amortized:.2f}"]) + + # Update Grand Totals + grand_totals['blended'] += blended + grand_totals['unblended'] += unblended + grand_totals['amortized'] += amortized + + # Sorting by cost (Amortized/Total) descending + rows.sort(key=lambda x: float(x[5]), reverse=True) + + # Write to CSV + filename = f"aws_cost_{start_date[:7]}.csv" + with open(filename, 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow(['Service', 'Usage Quantity', 'Unit', 'Blended Cost', 'Unblended Cost', 'Total (Amortized)']) + writer.writerows(rows) + # Grand Total Line + writer.writerow(['GRAND TOTAL', 'NA', 'NA', + f"{grand_totals['blended']:.2f}", + f"{grand_totals['unblended']:.2f}", + f"{grand_totals['amortized']:.2f}"]) + + print(f"✅ Success! Report saved as {filename}") + + except ClientError as e: + print(f"❌ Error: {e}") + +if __name__ == "__main__": + fetch_aws_costs()