fix(meta-ads): currency-offset-aware budget conversions (JPY and other zero-decimal currencies)#372
Merged
Merged
Conversation
…r zero-decimal currencies) Meta expresses budgets/bids in the currency's minor units with a per-currency offset (100 for USD-like; 1 for zero-decimal: JPY, KRW, TWD, ...). Blanket /100, *10_000 and //10_000 conversions silently scaled zero-decimal-currency money 100x in both directions. - core/providers/models.py: ZERO_DECIMAL_CURRENCIES + META_OFFSET_100_CURRENCIES whitelists (transcribed from the official Meta currencies table) and minor_units_per_unit(), which fail-fasts on unrecognized codes — a wrong guess (typo 'JPN') would otherwise send a 100x-too-large budget to the live API. - meta_ads/mappers.py: _cents_to_amount -> _minor_units_to_amount; map_campaign / map_ad_set take a required currency kwarg (exported API; the MCP read path is raw minor-unit passthrough and unchanged). - adapters/meta_ads: read (minor units -> micros) and write (micros -> minor units, negative-rejecting) conversions are currency-aware; MetaAdsAdapter requires the account currency at construction. - Stale 'in cents' docstrings corrected; _mureo-strategy SKILL.md now states guardrail caps compare Meta budgets in Meta's native minor units (identical to currency units for JPY). Found during a units audit of #366/#367; the previously wired MCP paths were already correct — the affected mappers/adapter were latent. Claude-Session: https://claude.ai/code/session_0195WBK6YopTveq9RAQE2xMz
Merged
hyoshi
added a commit
that referenced
this pull request
Jul 9, 2026
Google Ads read-only demographic/audience criteria + image assets (#369), budget type get/set (#371); Meta Ads ad-set end_time + lifetime_budget (#368); Search Console cross-client isolation / site_url tenant allow-list (#375, security); About Installed-packages consistency (#365); Meta budget zero-decimal currency fix (#372).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Units audit follow-up to #366/#367. Meta expresses budgets/bids in the currency's minor units with a per-currency offset — 100 for USD-like currencies, 1 for zero-decimal currencies (JPY, KRW, TWD, VND, IDR, HUF, ISK, CLP, COP, CRC, PYG) per the official Marketing API currencies table. Three places hardcoded the offset-100 assumption, silently scaling zero-decimal-currency money 100x:
meta_ads/mappers.py_cents_to_amount(÷100)adapters/meta_ads/mappers.py(×10,000)daily_budget_micros1/100 — inconsistent with the correctly-convertedspendmicros in the same modeladapters/meta_ads/adapter.py(÷10,000)Fix
core/providers/models.py:ZERO_DECIMAL_CURRENCIES+META_OFFSET_100_CURRENCIESwhitelists (both transcribed from Meta's table; CRC's offset-1 membership double-checked) andminor_units_per_unit(currency). Unrecognized codes raiseValueError— security review showed a typo like"JPN"falling back to offset 100 would 100x-overstate the write; fail-fast also converts future Meta-table drift into a loud error instead of silent mis-scaling.meta_ads/mappers.py:_minor_units_to_amount(value, currency);map_campaign/map_ad_setgain a required keyword-onlycurrency(breaking for the exported-but-unused API — a default would reintroduce the bug).adapters/meta_ads/: both directions currency-aware (JPY ×/÷1e6, USD ×/÷1e4, exact integer arithmetic); write path rejects negative micros;MetaAdsAdapter.__init__now requires the account currency (readable fromGET /act_<id>?fields=currency)._mureo-strategySKILL.md guardrail bullets now state Meta budget caps compare in Meta's native minor units (= currency units for JPY-like currencies; cents for USD-like).spendconversions (Meta returns spend in major units) verified correct and untouched. Google Ads micros paths unaffected.Test plan
tests/test_currency_offset.py): both directions × JPY/USD, whitelist parity with Meta docs, disjoint-sets, typo"JPN"/ unknown"XXX"fail fast (helper + write path), negative rejection, required/blank currency on the adaptercurrency="USD"(semantics preserved)Reviews: security-reviewer (HIGH unknown-code fallback → fail-fast fix → APPROVE, whitelists verified disjoint) + python-reviewer (arithmetic verified both directions, round-trip exact → CONFIRM) + /code-review checklist.
https://claude.ai/code/session_0195WBK6YopTveq9RAQE2xMz