| 1 | #!/usr/bin/env python3 |
| 2 | """Tiered degradation selector. |
| 3 | |
| 4 | Determines the appropriate service tier based on estimated cost and |
| 5 | monthly budget consumption, outputting a JSON configuration. |
| 6 | """ |
| 7 | |
| 8 | from __future__ import annotations |
| 9 | |
| 10 | import argparse |
| 11 | import json |
| 12 | import sys |
| 13 | |
| 14 | # Tier definitions |
| 15 | TIERS = { |
| 16 | "normal": {"model": "claude-sonnet-4", "max_repos": None, "skip_ai": False}, |
| 17 | "budget": {"model": "gpt-5.4-mini", "max_repos": 100, "skip_ai": False}, |
| 18 | "minimal": {"model": "gpt-5-mini", "max_repos": 30, "skip_ai": False}, |
| 19 | "emergency": {"model": None, "max_repos": None, "skip_ai": True}, |
| 20 | } |
| 21 | |
| 22 | DEFAULT_MONTHLY_BUDGET = 10.00 |
| 23 | |
| 24 | |
| 25 | def parse_args(argv: list[str] | None = None) -> argparse.Namespace: |
| 26 | parser = argparse.ArgumentParser(description="Select service tier based on budget status.") |
| 27 | parser.add_argument( |
| 28 | "--estimated-cost", |
| 29 | type=float, |
| 30 | default=0.0, |
| 31 | help="Estimated cost of the upcoming run in USD.", |
| 32 | ) |
| 33 | parser.add_argument( |
| 34 | "--monthly-spent", |
| 35 | type=float, |
| 36 | default=0.0, |
| 37 | help="Total USD spent this month so far.", |
| 38 | ) |
| 39 | parser.add_argument( |
| 40 | "--monthly-budget", |
| 41 | type=float, |
| 42 | default=DEFAULT_MONTHLY_BUDGET, |
| 43 | help="Monthly budget cap in USD.", |
| 44 | ) |
| 45 | return parser.parse_args(argv) |
| 46 | |
| 47 | |
| 48 | def select_tier( |
| 49 | estimated_cost: float, monthly_spent: float, monthly_budget: float = DEFAULT_MONTHLY_BUDGET |
| 50 | ) -> str: |
| 51 | """Determine tier based on thresholds.""" |
| 52 | if monthly_spent >= monthly_budget: |
| 53 | return "emergency" |
| 54 | if monthly_spent >= 8.00: |
| 55 | return "minimal" |
| 56 | if estimated_cost >= 0.50 or monthly_spent >= 5.00: |
| 57 | return "budget" |
| 58 | return "normal" |
| 59 | |
| 60 | |
| 61 | def build_config(tier: str) -> dict: |
| 62 | """Build output config dict for the given tier.""" |
| 63 | cfg = TIERS[tier].copy() |
| 64 | cfg["tier"] = tier |
| 65 | return { |
| 66 | "tier": cfg["tier"], |
| 67 | "model": cfg["model"], |
| 68 | "max_repos": cfg["max_repos"], |
| 69 | "skip_ai": cfg["skip_ai"], |
| 70 | } |
| 71 | |
| 72 | |
| 73 | def main(argv: list[str] | None = None) -> int: |
| 74 | args = parse_args(argv) |
| 75 | tier = select_tier(args.estimated_cost, args.monthly_spent, args.monthly_budget) |
| 76 | config = build_config(tier) |
| 77 | print(json.dumps(config)) |
| 78 | return 0 |
| 79 | |
| 80 | |
| 81 | if __name__ == "__main__": |
| 82 | sys.exit(main()) |