{
  "output": "Successfully created and wrote to new file: /Users/bob/git/dxw/aws-stuff/audits/bills.py. Here is the updated code:\nimport boto3\nfrom botocore.exceptions import ClientError\nfrom aws_utils import get_account_names, get_last_n_months_ranges, get_boto_session\n\ndef fetch_bills(ce_client, ranges):\n    \"\"\"\n    Fetches unblended and amortized costs across given ranges.\n    Returns a dict: { account_id: { \"unblended\": [m1, m2, m3], \"amortized\": [m1, m2, m3] } }\n    \"\"\"\n    account_costs = {}\n    \n    for start_date, end_date, _ in ranges:\n        try:\n            response = ce_client.get_cost_and_usage(\n                TimePeriod={\"Start\": start_date, \"End\": end_date},\n                Granularity=\"MONTHLY\",\n                Metrics=[\"UnblendedCost\", \"AmortizedCost\"],\n                GroupBy=[{\"Type\": \"DIMENSION\", \"Key\": \"LINKED_ACCOUNT\"}],\n            )\n\n            for result in response[\"ResultsByTime\"]:\n                for group in result[\"Groups\"]:\n                    account_id = group[\"Keys\"][0]\n                    unblended = float(group[\"Metrics\"][\"UnblendedCost\"][\"Amount\"])\n                    amortized = float(group[\"Metrics\"][\"AmortizedCost\"][\"Amount\"])\n\n                    if account_id not in account_costs:\n                        account_costs[account_id] = {\"unblended\": [], \"amortized\": []}\n\n                    account_costs[account_id][\"unblended\"].append(unblended)\n                    account_costs[account_id][\"amortized\"].append(amortized)\n        except ClientError as e:\n            print(f\"Error fetching cost for range {start_date} to {end_date}: {e}\")\n            \n    return account_costs\n\ndef run():\n    session = get_boto_session()\n    ce_client = session.client(\"ce\")\n    account_names = get_account_names()\n\n    ranges = get_last_n_months_ranges(3)\n    months_labels = [r[2] for r in ranges]\n\n    account_costs = fetch_bills(ce_client, ranges)\n\n    # Sort accounts by the most recent month's amortized cost\n    sorted_accounts = sorted(\n        account_costs.items(),\n        key=lambda item: item[1][\"amortized\"][0] if item[1][\"amortized\"] else 0,\n        reverse=True,\n    )\n\n    print(f\"\\nAWS Costs for the last 3 months (Amortized and Unblended, sorted by highest Amortized Cost in {months_labels[0]}):\")\n    header = f\"{'Account Name':<30} {' ' + months_labels[0] + ' (Amort)':<15} {' ' + months_labels[1] + ' (Amort)':<15} {' ' + months_labels[2] + ' (Amort)':<15} {' ' + months_labels[0] + ' (Unbl)':<15} {' ' + months_labels[1] + ' (Unbl)':<15} {' ' + months_labels[2] + ' (Unbl)':<15}\"\n    print(header)\n    print(\"-\" * 125)\n\n    for account_id, costs in sorted_accounts:\n        name = account_names.get(account_id, f\"Unknown ({account_id})\")\n\n        # Ensure we have 3 months of data\n        amortized = costs[\"amortized\"] + [0.0] * (3 - len(costs[\"amortized\"]))\n        unblended = costs[\"unblended\"] + [0.0] * (3 - len(costs[\"unblended\"]))\n\n        amortized_fmt = [f\"${c:,.2f}\" for c in amortized[:3]]\n        unblended_fmt = [f\"${c:,.2f}\" for c in unblended[:3]]\n\n        print(\n            f\"{name[:30]:<30} {amortized_fmt[0]:>15} {amortized_fmt[1]:>15} {amortized_fmt[2]:>15} {unblended_fmt[0]:>15} {unblended_fmt[1]:>15} {unblended_fmt[2]:>15}\"\n        )\n\nif __name__ == \"__main__\":\n    run()\n"
}