Skip to content

Plan and monitor Google Ads in the account's own currency - #402

Merged
demegire merged 1 commit into
mainfrom
feat/ads-multicurrency
Oct 12, 2026
Merged

demegire merged 1 commit into
mainfrom
feat/ads-multicurrency

Conversation

@demegire

Copy link
Copy Markdown
Contributor

What and why

Google Ads reads every amount in the account's own currency (CampaignBudget). Launch and monitor treated those amounts as dollars, so a $20 plan became ₺20 a day in a lira account. This replaces #400, which would have required USD accounts, with support for other currencies.

  • Launch reads the account's currency. For anything other than USD, it reads the day's ECB reference rate once and saves it as gather:fx. A failed read is retryable and writes nothing.
  • The plan works out budget, click ceiling and cost-per-customer targets in dollars as before, then converts each once onto Google's minimum unit (whole yen, won, rupiah; cents elsewhere). The $1 budget and $0.05 ceiling floors still apply at the converted rate. PLAN.md and the approval summary show the account-currency amount, its dollar equivalent and the rate. The rate is pinned on the plan and in campaign.json and never converted again.
  • Supported currencies are those Google Ads bills in and the ECB publishes (28). Any other, or a missing currency, gates as unsupported_currency before any rate read, model call or write.
  • Request builders check the unit, the converted floors and a runaway cap of $1M equivalent per currency.
  • The monitor reads the account's currency each run. Reports, rationales and proposals use it, and proposals carry the currency and rate. The _usd field names on Google Ads amounts are renamed, since they never were dollars.
  • Compatibility: plans and proposals saved before this change read as USD, which they were. apply refuses a plan whose currency differs from the account's. A dollar-planned campaign in a non-USD account gets a currency_mismatch alert, plain cost reporting and no cost-based pauses or proposals.

Verification

  • New tests/test_paid_ads_currency.py covers:
    • cross rates through the euro and feed failures;
    • rounding and floors;
    • JPY and IDR bundles, with refusals for off-unit or below-floor amounts;
    • a EUR launch end to end with one rate read;
    • unsupported and missing currencies stopping before any rate, model or write;
    • a rate outage that retries;
    • currency-mismatch refusal at apply, and a legacy plan still applying in dollars;
    • monitor proposals in EUR and whole-yen steps, and the mismatch alert.
  • Existing paid ads, Google Ads and proposal tests were updated for the renamed fields. Legacy campaign.json and proposal fixtures were kept as back-compat coverage.
  • Full suite locally: 4,602 passed, 1,257 skipped (DB-backed, no local Postgres; CI covers them). Ruff, format and import-linter pass.
  • A live ECB read through ads_currency converted $20 to €17.89, £15.11, ¥3,172 and ₹1,916.

Notes

  • No live Google Ads account was used. One real non-USD launch is still needed before calling this done.
  • Launch inputs (daily_budget_usd, cpc_ceiling_usd) stay in USD and are converted, with both amounts shown before approval. The assessment still reasons in USD.
  • Launch and monitor policy versions are bumped to 2. A monitor run in progress during the deploy may fail once on renamed fields; the next run is unaffected.
  • The ECB read is a fixed public GET from the worker. It needs no credentials and costs nothing.

🤖 Generated with Claude Code

Google reads every amount in the account's currency, but launch and monitor
treated them as dollars. A launch now reads the account's currency, pins one
ECB reference rate, converts the plan's dollar figures once onto Google's
minimum unit and shows both before approval. The monitor reports and proposes
in that currency. Currencies Tin cannot price stop as unsupported_currency.
Plans and proposals saved before this read as USD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@demegire
demegire requested a review from a team as a code owner October 12, 2026 14:45
@demegire
demegire merged commit c52e0c3 into main Oct 12, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant