#!/usr/bin/env python3 """Seleção conservadora por papel; custos OpenRouter nunca são tarifa de cobrança.""" from __future__ import annotations import datetime as dt import json from decimal import Decimal, InvalidOperation from pathlib import Path from typing import Any, Iterable from model_catalog import (AA_METRIC, AA_VERSION, CACHE_DIR, decimal_text, decimal_value, discover_local_models, load_snapshot, openrouter_price_for_profile, reference_cost) ROLE_BANDS = { "explorer": "leve", "verifier": "leve", "summarizer": "leve", "implementer": "media", "ui-critic": "media", "planner": "alta", "reviewer": "alta", "security-auditor": "alta", } DEFAULT_DELTA = {"leve": Decimal(3), "media": Decimal(3), "alta": Decimal(2)} EXPECTED_HARNESSES = {"opencode": "OpenCode", "omp": "OMP", "claude": "Claude Code"} WINDOWS = ("5h", "weekly", "monthly") FIXED_BANDS = { "leve": (Decimal(10), Decimal(20)), "media": (Decimal(20), Decimal(50)), "alta": (Decimal(50), Decimal(100)), } def validate_bands(bands: dict[str, Any] | None) -> dict[str, tuple[Decimal, Decimal]]: """Aceita a configuração declarativa apenas se preservar as faixas normativas.""" if bands is None: return FIXED_BANDS.copy() if not isinstance(bands, dict) or set(bands) != set(FIXED_BANDS): raise ValueError("bands deve conter exatamente leve/media/alta") normalized = {} for name, limits in bands.items(): if not isinstance(limits, (list, tuple)) or len(limits) != 2: raise ValueError(f"faixa {name} deve declarar dois limites") low, high = (_decimal(value) for value in limits) if low is None or high is None: raise ValueError(f"limites da faixa {name} devem ser decimais") normalized[name] = (low, high) if normalized != FIXED_BANDS: raise ValueError("bands customizadas são recusadas; use 10..20, 20..50 e 50..100") return normalized def _score_band(score: Decimal | None, bands: dict[str, tuple[Decimal, Decimal]]) -> str | None: if score is None: return None for name in ("leve", "media"): low, high = bands[name] if low <= score < high: return name low, high = bands["alta"] return "alta" if low <= score <= high else None def _time(value: Any) -> dt.datetime | None: if not isinstance(value, str) or not value: return None try: result = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError: return None if result.tzinfo is None: return None return result.astimezone(dt.timezone.utc) def _decimal(value: Any) -> Decimal | None: try: return decimal_value(value) except ValueError: return None def _has_token_profile(tokens: dict[str, Any]) -> bool: return any((_decimal(tokens.get(key)) or Decimal(0)) > 0 for key in ("input", "output", "cache_read", "cache_write")) _PAID_MODES = ("zen_balance", "go_quota", "omp_payg", "claude_subscription") _BALANCE_MODES = ("zen_balance", "omp_payg") _SIXTY_SECONDS = dt.timedelta(seconds=60) _DAY = dt.timedelta(hours=24) def _expired(moment: dt.datetime | None, now: dt.datetime, limit: dt.timedelta) -> bool: """Ausente, no futuro ou mais antigo que o limite.""" return moment is None or moment > now or now - moment > limit def _is_https(value: Any) -> bool: return str(value).startswith("https://") def _native_tariff_stale(native: dict[str, Any], now: dt.datetime, limit: dt.timedelta) -> bool: return (native.get("account_confirmed") is not True or not _is_https(native.get("source_url", "")) or _expired(_time(native.get("captured_at")), now, limit)) def _openrouter_record(row: dict[str, Any]) -> dict[str, Any]: return row.get("openrouter") or {"price_per_token_usd": row.get("or_price_per_token") or {}} def _go_quota_stale(row: dict[str, Any], now: dt.datetime) -> bool: return (_expired(_time(row.get("quota_verified_at")), now, _SIXTY_SECONDS) or not _is_https(row.get("quota_source_url", ""))) def _reference_price_stale(price: dict[str, Any], now: dt.datetime, limit: dt.timedelta) -> bool: """Só há o que revalidar quando existe alguma tarifa referencial.""" rates = price.get("price_per_token_usd", {}) has_reference_price = any(rates.get(k) is not None for k in ("input", "output", "cache_read", "cache_write")) return has_reference_price and _expired(_time(price.get("captured_at")), now, limit) def _availability_reasons(row: dict[str, Any], mode: Any, now: dt.datetime) -> list[str]: reasons: list[str] = [] availability = row.get("availability") or {} if _expired(_time(availability.get("local_verified_at") or availability.get("verified_at")), now, _DAY): reasons.append("disponibilidade_local_precisa_revalidacao") if (availability.get("authenticated") is True and availability.get("entitled") is True and _expired(_time(availability.get("account_verified_at")), now, _DAY)): reasons.append("entitlement_precisa_revalidacao") if mode in _BALANCE_MODES and (availability.get("balance_confirmed") is not True or _expired(_time(availability.get("balance_verified_at")), now, _SIXTY_SECONDS)): reasons.append("saldo_nao_confirmado_ou_obsoleto") return reasons def _staleness(row: dict[str, Any], now: dt.datetime, max_price_age_hours: Decimal, max_score_age_days: Decimal, tokens: dict[str, Any]) -> list[str]: mode = row.get("billing_mode") price = openrouter_price_for_profile(_openrouter_record(row), tokens) if mode not in _PAID_MODES: return [] reasons: list[str] = [] price_limit = dt.timedelta(seconds=float(max_price_age_hours * Decimal(3600))) if _reference_price_stale(price, now, price_limit): reasons.append("preco_referencial_obsoleto_ou_ausente") score_limit = dt.timedelta(seconds=float(max_score_age_days * Decimal(86400))) if _expired(_time((row.get("score") or {}).get("observed_at")), now, score_limit): reasons.append("benchmark_obsoleto_ou_ausente") if mode in _BALANCE_MODES and _native_tariff_stale(row.get("native_tariff") or {}, now, price_limit): reasons.append("tarifa_nativa_obsoleta_ou_nao_confirmada") if mode == "go_quota" and _go_quota_stale(row, now): reasons.append("quota_go_obsoleta_ou_nao_confirmada") return reasons + _availability_reasons(row, mode, now) def _profile_cost(row: dict[str, Any], tokens: dict[str, Any]) -> Decimal | None: profile = openrouter_price_for_profile(_openrouter_record(row), tokens) return reference_cost(profile.get("price_per_token_usd") or {}, tokens) _CostResult = tuple[Decimal | None, Decimal | None, str | None] def _has_confirmed_rates(native: Any) -> bool: return (isinstance(native, dict) and isinstance(native.get("price_per_token_usd"), dict) and native.get("account_confirmed") is True) def _balance_cost(row: dict[str, Any], tokens: dict[str, Any]) -> _CostResult: """zen_balance e omp_payg: custo pela tarifa nativa confirmada e folga de saldo.""" native = row.get("native_tariff") if not _has_token_profile(tokens): return None, None, "perfil_tokens_native_nao_configurado" if not _has_confirmed_rates(native): return None, None, "tarifa_native_nao_confirmada" amount = reference_cost(native["price_per_token_usd"], tokens) if amount is None: return None, None, "tarifa_native_incompleta_para_perfil" balance = _decimal(native.get("available_balance_usd")) if (row.get("availability") or {}).get("balance_confirmed") is not True: return None, None, "saldo_nao_confirmado" if balance is None or balance < 0: return None, None, "saldo_desconhecido" if balance < amount: return None, balance, "saldo_insuficiente" return amount, balance - amount, None def _go_window_headrooms(remaining: dict[str, Any], debit_map: dict[str, Any]) -> tuple[list[Decimal], str | None]: """Folga por janela; no primeiro problema devolve a razão de recusa.""" headrooms: list[Decimal] = [] for window in WINDOWS: available = _decimal(remaining.get(window)) used = _decimal(debit_map.get(window)) if available is None or used is None or available < 0 or used < 0: return headrooms, f"janela_go_{window}_desconhecida" if used > available: return headrooms, f"quota_go_{window}_insuficiente" headrooms.append(available - used) return headrooms, None def _go_quota_cost(row: dict[str, Any], tokens: dict[str, Any]) -> _CostResult: """go_quota: débito mensal como custo representativo e a menor folga entre as janelas.""" if not _has_token_profile(tokens): return None, None, "perfil_tokens_go_nao_configurado" remaining = row.get("quota_window_remaining") debit = row.get("quota_window_debit") if row.get("use_balance_fallback"): return None, None, "fallback_go_para_saldo_zen_nao_autorizado" if not isinstance(remaining, dict) or debit is None: return None, None, "quota_go_nao_confirmada" debit_map = debit if isinstance(debit, dict) else {window: debit for window in WINDOWS} headrooms, reason = _go_window_headrooms(remaining, debit_map) if reason: return None, None, reason representative = _decimal(debit_map.get("monthly")) if representative is None: return None, min(headrooms), "debito_go_nao_confirmado" return representative, min(headrooms), None def _subscription_cost(row: dict[str, Any], tokens: dict[str, Any]) -> _CostResult: amount = _decimal(row.get("subscription_consumption_usd")) if amount is None: return None, None, "consumo_assinatura_nao_mensuravel" return amount, None, None _COST_BY_MODE = {"zen_balance": _balance_cost, "omp_payg": _balance_cost, "go_quota": _go_quota_cost, "claude_subscription": _subscription_cost} def _native_cost_and_headroom(row: dict[str, Any], tokens: dict[str, Any]) -> _CostResult: mode = row.get("billing_mode") handler = _COST_BY_MODE.get(mode) if isinstance(mode, str) else None if handler is None: return None, None, "modo_de_cobranca_desconhecido" return handler(row, tokens) def route(candidates: Iterable[dict[str, Any]], role: str, *, allowed_providers: Iterable[str] = (), tokens: dict[str, Any] | None = None, budget_usd: Any = None, allow_proxy: bool = False, delta_points: Any = None, max_price_age_hours: Any = 24, max_score_age_days: Any = 30, now: dt.datetime | None = None, band_override: str | None = None, bands: dict[str, Any] | None = None, benchmark_metric: str = "aa_coding_agent_index_v1.5") -> dict[str, Any]: """Returns the stable API: selected, ranking, reason. Never dispatches a model.""" default_band = ROLE_BANDS.get(role) if default_band is None: return {"selected": None, "ranking": [], "reason": f"papel_desconhecido:{role}"} normalized_bands = validate_bands(bands) band = band_override or default_band if band not in normalized_bands: raise ValueError("band deve ser uma faixa fixa: leve, media ou alta") if benchmark_metric != "aa_coding_agent_index_v1.5": raise ValueError("benchmark_metric incompatível; somente aa_coding_agent_index_v1.5 é aceito") if now is None: now = dt.datetime.now(dt.timezone.utc) if now.tzinfo is None: raise ValueError("now precisa de timezone") now = now.astimezone(dt.timezone.utc) tokens = tokens or {} providers = set(allowed_providers) if not providers: return {"selected": None, "ranking": [], "reason": "provider_nao_autorizado; informe --providers explicitamente"} if budget_usd is None: budget = None else: budget = _decimal(budget_usd) if budget is None or budget < 0: raise ValueError("budget_usd precisa ser decimal não negativo") max_price_age = _decimal(max_price_age_hours) max_score_age = _decimal(max_score_age_days) if max_price_age is None or max_score_age is None: raise ValueError("TTL precisa ser decimal não negativo") delta = _decimal(delta_points) if delta_points is not None else DEFAULT_DELTA[band] if delta is None or delta < 0 or max_price_age < 0 or max_score_age < 0: raise ValueError("delta e TTL devem ser decimais não negativos") ranking = [] eligible = [] reasons_seen: list[str] = [] for row in candidates: if not isinstance(row, dict): continue provider = row.get("provider") score = row.get("score") or {} parsed_score = _decimal(score.get("score")) row_band = _score_band(parsed_score, normalized_bands) reference = _profile_cost(row, tokens) native_cost, headroom, native_error = _native_cost_and_headroom(row, tokens) excluded: list[str] = [] availability = row.get("availability") or {} if provider not in providers: excluded.append("provider_nao_autorizado") if availability.get("local_listed") is not True: excluded.append("nao_listado_localmente") if availability.get("authenticated") is not True: excluded.append("autenticacao_nao_confirmada") if availability.get("entitled") is not True: excluded.append("entitlement_nao_confirmado") required_provenance = all(score.get(field) not in (None, "", {}) for field in ("agent", "harness", "settings", "source_url", "observed_at")) if not required_provenance or not str(score.get("source_url", "")).startswith("https://artificialanalysis.ai/"): excluded.append("proveniencia_do_benchmark_incompleta") if score.get("variant") != row.get("variant"): excluded.append("mapeamento_de_variante_incompativel") expected_harness = EXPECTED_HARNESSES.get(row.get("tool")) if score.get("match_grade") == "exact" and score.get("harness") != expected_harness: excluded.append("harness_incompativel") if score.get("metric_id") != AA_METRIC or str(score.get("version")) != AA_VERSION: excluded.append("indice_incompativel_ou_ausente") if parsed_score is None: excluded.append("no_scored_candidate") if score.get("match_grade") not in (("exact", "proxy") if allow_proxy else ("exact",)): excluded.append("match_proxy_nao_autorizado" if score.get("match_grade") == "proxy" else "mapeamento_de_variante_incompativel") if row_band != band: excluded.append("fora_da_faixa" if row_band else "sem_faixa_de_nota") excluded.extend(_staleness(row, now, max_price_age, max_score_age, tokens)) if native_error: excluded.append(native_error) if row.get("billing_mode") in ("zen_balance", "omp_payg") and budget is None: excluded.append("orcamento_zen_nao_configurado" if row.get("billing_mode") == "zen_balance" else "orcamento_payg_omp_nao_configurado") if native_cost is not None and budget is not None and native_cost > budget: excluded.append("orcamento_excedido") if row.get("billing_mode") == "claude_subscription" and native_cost is None: excluded.append("consumo_assinatura_nao_mensuravel") if row.get("billing_mode") == "go_quota" and headroom is None and native_error is None: excluded.append("cota_go_nao_confirmada") key = row.get("model_key") or f"{provider}/{row.get('model_id', '')}" entry = { "model_key": key, "provider": provider, "billing_mode": row.get("billing_mode"), "score": decimal_text(parsed_score), "band": row_band, "match_grade": score.get("match_grade"), "eligible": not excluded, "exclusion_reasons": excluded, "reference_cost_usd": decimal_text(reference), "native_cost_or_quota_debit_usd": decimal_text(native_cost), "quota_headroom_usd": decimal_text(headroom), } ranking.append(entry) if not excluded: eligible.append((row, entry, parsed_score, native_cost, headroom)) else: reasons_seen.extend(excluded) ranking.sort(key=lambda entry: ( not entry["eligible"], _decimal(entry.get("native_cost_or_quota_debit_usd")) if _decimal(entry.get("native_cost_or_quota_debit_usd")) is not None else Decimal("Infinity"), -(_decimal(entry.get("score")) or Decimal(0)), -(_decimal(entry.get("quota_headroom_usd")) or Decimal(0)), entry["model_key"], )) if not ranking: return {"selected": None, "ranking": [], "reason": "no_scored_candidate"} if not eligible: if "no_scored_candidate" in reasons_seen or "indice_incompativel_ou_ausente" in reasons_seen: reason = "no_scored_candidate_or_compatible_index" elif "fora_da_faixa" in reasons_seen or "sem_faixa_de_nota" in reasons_seen: reason = f"faixa_vazia:{band}" elif "quota_go_5h_insuficiente" in reasons_seen or "quota_go_weekly_insuficiente" in reasons_seen or "quota_go_monthly_insuficiente" in reasons_seen: reason = "quota_go_insuficiente; execução pausada antes da chamada" elif "fallback_go_para_saldo_zen_nao_autorizado" in reasons_seen: reason = "fallback_go_para_saldo_zen_nao_autorizado" else: reason = "nenhum_candidato_elegivel:" + ",".join(sorted(set(reasons_seen))) return {"selected": None, "ranking": ranking, "reason": reason} top_score = max(item[2] for item in eligible) plateau = [item for item in eligible if item[2] >= top_score - delta] # custo real da ferramenta; desempate: score, folga de quota, ID lexical. plateau.sort(key=lambda item: (item[3], -item[2], -(item[4] if item[4] is not None else Decimal(0)), item[1]["model_key"])) chosen = plateau[0] row, entry, score_value, native_cost, headroom = chosen selected = { "model_key": entry["model_key"], "billing_mode": {"zen_balance": "zen", "go_quota": "go", "omp_payg": "payg", "claude_subscription": "subscription"}.get(row.get("billing_mode"), row.get("billing_mode")), "billing_mode_detail": row.get("billing_mode"), "score": entry["score"], "band": band, "match_grade": entry["match_grade"], "reference_cost_usd": entry["reference_cost_usd"], "native_cost_or_quota_debit_usd": decimal_text(native_cost), "estimated_native_usd": decimal_text(native_cost) if row.get("billing_mode") in ("zen_balance", "omp_payg") else None, "native_tariff": row.get("native_tariff"), "available_balance_usd": (row.get("native_tariff") or {}).get("available_balance_usd"), "balance_confirmed": (row.get("availability") or {}).get("balance_confirmed") is True, "balance_verified_at": (row.get("availability") or {}).get("balance_verified_at"), "quota_window_remaining": row.get("quota_window_remaining"), "quota_window_debit": row.get("quota_window_debit"), "use_balance_fallback": row.get("use_balance_fallback") if row.get("billing_mode") == "go_quota" else False, "quota_headroom_usd": decimal_text(headroom), "score_top": decimal_text(top_score), "plateau_delta_points": decimal_text(delta), } if row.get("tool") == "omp": variant = row.get("variant") selected["dispatch_model_key"] = f"{row['provider']}/{row['model_id']}" + (f":{variant}" if variant else "") reason = f"menor consumo relevante dentro do platô {band} (score >= {decimal_text(top_score - delta)}); custo OpenRouter é apenas referência" if len(plateau) > 1: reason += "; desempate determinístico por consumo, score, folga de cota e model_key" return {"selected": selected, "ranking": ranking, "reason": reason} def route_from_catalog(tool: str, role: str, allowed_providers: Iterable[str], cache_dir: str | Path | None = None, *, explain: bool = False, tokens: dict[str, Any] | None = None, budget_usd: Any = None, allow_proxy: bool = False, delta_points: Any = None, max_price_age_hours: Any = 24, max_score_age_days: Any = 30, band_override: str | None = None, bands: dict[str, Any] | None = None, benchmark_metric: str = "aa_coding_agent_index_v1.5", now: dt.datetime | None = None, local_discoverer=discover_local_models, omp_discoverer=None) -> dict[str, Any]: from model_catalog import discover_omp_models clock = now or dt.datetime.now(dt.timezone.utc) snapshot = load_snapshot(tool, cache_dir) if snapshot is None: return {"selected": None, "ranking": [], "reason": "snapshot_de_catalogo_ausente; execute models sync"} if tool not in ("opencode", "omp"): return {"selected": None, "ranking": [], "reason": f"catalogo_local_nao_disponivel_para_{tool}"} providers = tuple(allowed_providers) if not providers: return route(snapshot.get("models", []), role, allowed_providers=providers, tokens=tokens, budget_usd=budget_usd, allow_proxy=allow_proxy, delta_points=delta_points, max_price_age_hours=max_price_age_hours, max_score_age_days=max_score_age_days, band_override=band_override, bands=bands, benchmark_metric=benchmark_metric, now=clock) discoverer = local_discoverer if tool == "opencode" else (omp_discoverer or discover_omp_models) discoveries = {provider: discoverer(provider) for provider in sorted(set(providers))} verified_at = clock.astimezone(dt.timezone.utc).isoformat().replace("+00:00", "Z") candidates = [] for original in snapshot.get("models", []): row = dict(original) availability = dict(row.get("availability") or {}) provider = row.get("provider") result = discoveries.get(provider) if result is not None and result.get("ok"): local_ids = set(result.get("models", [])) local_model_key = row["model_key"] if tool == "omp" else row["model_id"] + (f"#{row['variant']}" if row.get("variant") else "") availability["local_listed"] = local_model_key in local_ids availability["local_verified_at"] = result.get("verified_at", verified_at) elif provider in discoveries: availability["local_listed"] = None availability["local_verified_at"] = None row["availability"] = availability candidates.append(row) return route(candidates, role, allowed_providers=providers, tokens=tokens, budget_usd=budget_usd, allow_proxy=allow_proxy, delta_points=delta_points, max_price_age_hours=max_price_age_hours, max_score_age_days=max_score_age_days, band_override=band_override, bands=bands, benchmark_metric=benchmark_metric, now=clock) def main(argv: list[str] | None = None) -> int: import argparse parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--tool", choices=("opencode", "omp", "claude"), default="opencode") parser.add_argument("--role", required=True) parser.add_argument("--providers", default="") parser.add_argument("--format", choices=("json",), default="json") parser.add_argument("--cache-dir") args = parser.parse_args(argv) result = route_from_catalog(args.tool, args.role, [p for p in args.providers.split(",") if p], args.cache_dir, explain=True) print(json.dumps(result, ensure_ascii=False)) return 0 if result["selected"] is not None else 2 if __name__ == "__main__": raise SystemExit(main())