Skip to content

feat(meta_ads): operator-specified canonical conversion event (#342)#345

Merged
hyoshi merged 1 commit into
mainfrom
feat/operator-conversion-events
Jun 27, 2026
Merged

feat(meta_ads): operator-specified canonical conversion event (#342)#345
hyoshi merged 1 commit into
mainfrom
feat/operator-conversion-events

Conversation

@hyoshi

@hyoshi hyoshi commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #340. The Meta conversion counter counts only a fixed deduped
generic set ({lead, purchase, complete_registration}), which under-counts
two account setups:

  1. Custom-event advertisers — a real conversion that is a custom pixel
    event (offsite_conversion.custom.<id>) reports 0 conversions, so CPA
    can't compute and daily-check / budget decisions go wrong.
  2. Component-only accounts — an account that emits only a component row
    (offsite_conversion.fb_pixel_lead) with no generic aggregate counts 0.

This lets the operator declare, per Meta ad account, exactly which
action_type(s) count as conversions
, overriding the default.

Design

  • Replacement semantics: the listed types are the complete conversion set
    — never summed on top of the generic set, so overlapping aliases can't
    double-count (preserves the bug(meta_ads): meta_row_conversions が action_type の部分一致でコンバージョンを過大計上(別名の重複+ViewContent混入) #340 fix). Unset → built-in default (no change
    for accounts that report it). Empty → falls back to default (a cleared
    setting never zeroes everything).
  • Storage: STATE.json platforms[<platform>].conversion_action_types
    (preserved across upsert_campaign / set_platform_metrics).
  • Setter: new MCP tool mureo_state_set_conversion_events.
  • Resolver load_conversion_action_types(account_id) reads the active
    workspace
    STATE.json via the runtime context (the same file the tool
    writes — agency / alternate-StateStore safe), tolerant of the act_
    prefix, and never raises (a malformed file → None, never breaks a live
    report). Threaded into all five live Meta conversion counters
    (meta_row_conversions, _extract_cv, _insights breakdown,
    _budget_efficiency, _live_clients aggregate/per-campaign,
    _summarise_meta_performance).
  • Skill: _mureo-meta-ads gains a "conversion definition" section telling
    the agent to look up the account's real action_type labels (via
    meta_ads_insights_report / _breakdown), confirm with the operator, then
    set the exact string(s) — so custom slugs aren't mistyped. No configure-UI
    change (operator-via-agent flow, per design discussion).

Test plan

  • count_conversions_from_actions override (replace / empty-fallback /
    component-only).
  • set_conversion_action_types + load_conversion_action_types round-trip,
    clear, account mismatch, never-raises on malformed/non-object JSON,
    act_ prefix tolerance, preservation across upsert/metrics, parse↔render.
  • MCP tool: set/clear, input validation, read/write path agreement via
    the runtime context.
  • ruff + black clean on mureo/; mypy --strict clean on changed
    modules; 1336 relevant tests pass (only the known installed-plugin
    env-gating noise excluded). Tool-count assertions + docs updated (188 → 189).
  • Reviewed by python-reviewer (adversarial); fixed two HIGH (read/write
    path mismatch, never-raises contract) + the act_ footgun + a str char-split
    edge — each with a regression test.

Known follow-up (out of scope)

The Protocol adapter's daily_report path (mureo/adapters/meta_ads/mappers.py)
uses its own hardcoded conversion set and is not yet covered by the override.

Closes #342

https://claude.ai/code/session_011rAu94b1o1xWYZhARk1VmL

#340 made the Meta conversion counter count only a fixed deduped generic set
({lead, purchase, complete_registration}). That under-counts two account
setups: a custom-event advertiser (offsite_conversion.custom.<id>, who reports
0 conversions), and an account that only emits a component row
(offsite_conversion.fb_pixel_lead) with no generic aggregate.

Let the operator declare, per Meta ad account, EXACTLY which action_type(s)
count as conversions — overriding the default (replacement semantics; the
listed types are the complete set, never summed on top of the generic set, so
overlapping aliases can't double-count). Unset keeps the built-in default, so
nothing changes for accounts that report it.

- Storage: STATE.json platforms[<platform>].conversion_action_types
  (mureo/context/models.py PlatformState + state.py parse/serialize; preserved
  across upsert_campaign / set_platform_metrics like totals).
- Setter: new MCP tool mureo_state_set_conversion_events
  (mureo/mcp/{tools,_handlers}_mureo_context.py). Empty list clears it.
- Counter: count_conversions_from_actions(actions, conversion_action_types=...)
  — override replaces the default; an empty collection falls back to default
  so a cleared setting never zeroes every conversion.
- Resolver: state.load_conversion_action_types(account_id) reads the ACTIVE
  workspace STATE.json via the runtime context (the SAME file the MCP tool
  writes — agency/alternate-StateStore safe), tolerant of the act_ prefix, and
  never raises (a malformed file yields None rather than breaking a live
  report). Threaded into ALL FIVE live Meta conversion counters so they apply
  the override consistently.
- Skill: _mureo-meta-ads gains a "conversion definition" section telling the
  agent to look up the account's real action_type labels, confirm with the
  operator, then call the tool (avoids mistyped custom slugs). Mirrored to
  mureo/_data/skills/.
- docs/mcp-server.md tool count 188 -> 189.

Known follow-up (out of the declared five-site scope): the Protocol adapter's
daily_report path (mureo/adapters/meta_ads/mappers.py) uses its own hardcoded
conversion set and is not yet covered by the override.

Reviewed by python-reviewer; fixed the read/write path mismatch, the
never-raises contract on non-object JSON, the act_ prefix-match footgun, and a
str char-split edge — each with a regression test.

Closes #342

Claude-Session: https://claude.ai/code/session_011rAu94b1o1xWYZhARk1VmL
@hyoshi hyoshi merged commit 7c31107 into main Jun 27, 2026
9 checks passed
@hyoshi hyoshi deleted the feat/operator-conversion-events branch June 27, 2026 00:29
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.

feat(meta_ads): operator-specified canonical conversion event (avoid component-only under-count)

1 participant