#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.10" # dependencies = ["boto3>=1.34"] # /// """List AWS Cost Anomaly Detection anomalies as compact JSON. Usage: uv run scripts/list_anomalies.py [--days 60] [--profile NAME] [--max-results 25] """ import argparse import json from datetime import date, timedelta import boto3 def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--days", type=int, default=60, help="lookback window in days (default: 60)") parser.add_argument("--profile", help="AWS profile name") parser.add_argument("--max-results", type=int, default=25, help="cap on anomalies returned (default: 25)") args = parser.parse_args() session = boto3.Session(profile_name=args.profile) # Cost Explorer is only served out of us-east-1 ce = session.client("ce", region_name="us-east-1") end = date.today() start = end - timedelta(days=args.days) anomalies = [] token = None while True: kwargs = { "DateInterval": {"StartDate": start.isoformat(), "EndDate": end.isoformat()}, "MaxResults": 100, } if token: kwargs["NextPageToken"] = token resp = ce.get_anomalies(**kwargs) anomalies.extend(resp.get("Anomalies", [])) token = resp.get("NextPageToken") if not token: break out = [] for a in anomalies: impact = a.get("Impact", {}) out.append( { "anomaly_id": a.get("AnomalyId"), "start": a.get("AnomalyStartDate"), "end": a.get("AnomalyEndDate"), "total_impact_usd": impact.get("TotalImpact"), "max_daily_impact_usd": impact.get("MaxImpact"), "total_actual_spend_usd": impact.get("TotalActualSpend"), "total_expected_spend_usd": impact.get("TotalExpectedSpend"), "anomaly_score": (a.get("AnomalyScore") or {}).get("MaxScore"), "feedback": a.get("Feedback"), "monitor_arn": a.get("MonitorArn"), # AWS's own root-cause hints (service/region/usage type/account) "root_cause_hints": [ {k: v for k, v in rc.items() if v} for rc in a.get("RootCauses", []) ], } ) out.sort(key=lambda x: x["start"] or "", reverse=True) out = out[: args.max_results] print(json.dumps({"lookback_days": args.days, "count": len(out), "anomalies": out}, indent=1, default=str)) if __name__ == "__main__": main()