{
  "id": "get_advertiser_report",
  "mcp_tool": "get_advertiser_report",
  "status": "approved",
  "source_system": "cake",
  "environment": ["staging", "prod"],
  "sharded": false,
  "connections": {
    "whale": "cake_whale_ro",
    "admin": "cake_admin_ro"
  },
  "objects": [
    "whale.lucos_ro_advertiser_stats_daily",
    "whale.lucos_ro_publisher_earnings_daily",
    "whale.lucos_ro_affiliate_stats_daily",
    "Admin.lucos_ro_adv_campaign_performance_daily",
    "Admin.lucos_ro_advertiser_goals",
    "Admin.lucos_ro_advertiser_categories",
    "Admin.lucos_ro_affiliate_directory"
  ],
  "parameters": {
    "advertiser_id": { "type": "int", "required": false },
    "affiliate_id": { "type": "int", "required": false },
    "publisher_id": { "type": "string", "required": false },
    "show_oos": { "type": "int", "required": false, "default": 0 },
    "group_by": {
      "type": "enum",
      "required": false,
      "default": "publisher",
      "values": [
        "publisher",
        "advertiser",
        "affiliate",
        "total",
        "account_manager",
        "account_sales_manager"
      ]
    },
    "sort_by": {
      "type": "enum",
      "required": false,
      "default": "clicks",
      "values": [
        "clicks",
        "spend",
        "advertiser_conversions",
        "sales_revenue",
        "unreported_conversions",
        "valid_clicks"
      ]
    },
    "sort_dir": {
      "type": "enum",
      "required": false,
      "default": "desc",
      "values": ["desc", "asc"]
    },
    "limit": { "type": "int", "required": false, "default": 5000 },
    "max_advertiser_conversions": { "type": "int", "required": false },
    "date_from": { "type": "date", "required": false },
    "date_to": { "type": "date", "required": false }
  },
  "constraints": {
    "max_window_days": 92,
    "default_window_days": 31,
    "max_rows": 5000,
    "timeout_ms": 60000
  },
  "allow_fields": [
    "adv_id",
    "adv_name",
    "affiliate_id",
    "affiliate_name",
    "account_manager",
    "account_sales_manager",
    "clicks",
    "valid_clicks",
    "earning",
    "total_conversions",
    "total_sales_revenue",
    "estimated_conversions",
    "advertiser_conversions",
    "unreported_conversions",
    "sales_revenue",
    "spend",
    "pub_earnings",
    "fraud_clicks",
    "dropped_clicks",
    "kpi",
    "cpa",
    "actual_cpa",
    "cpa_source",
    "goal_value",
    "goal_type",
    "category",
    "advertiser_count",
    "publisher_count"
  ],
  "deny_fields": ["aov", "rev_share", "postback_url", "ip"],
  "post_processing": [
    "Merge whale stats with Admin conversions on the group_by grain (publisher: adv_id+affiliate_id; advertiser: adv_id; affiliate: affiliate_id across advertisers; total: one window row). Cross-instance, so it cannot be a join — docs/CAKE-OPEN-DECISIONS.md OD-1.",
    "account_manager / account_sales_manager: whale.lucos_ro_affiliate_stats_daily grouped by the name on the stats row (not current MNGT assignment). Rank valid_clicks. show_oos defaults 0 — not an AM vs BD switch. This table has no adv_id; advertiser_id is ignored.",
    "sales_revenue = ROUND(SUM(conversion_value), 2). Rounded once after summing, matching the PHP; per-row rounding would drift.",
    "KPI = (goal / actual_CPA) * 100 when goal_type is CPA, else (actual_ROAS / goal) * 100. Ported from getAdvertiserStatsOptimized lines 354-361. Omitted when group_by=total, affiliate, account_manager, or account_sales_manager (no single advertiser goal).",
    "Average CPA is cpa (spend/estimated_conversions); actual/KPI CPA is actual_cpa (spend/advertiser_conversions). kpi is goal-hit %, not dollars. Omitted on total/affiliate/AM/BD grains and on summary.",
    "Always return summary with unbounded window totals (no LIMIT) so 'total spend across all advertisers' is answerable even when publisher-grain rows are truncated at max_rows.",
    "sort_by/sort_dir rank merged rows in process (conversions live on Admin, so SQL ORDER BY clicks is only a pre-sort). For 'best affiliate by conversions' use group_by=affiliate, omit advertiser_id, sort_by=advertiser_conversions.",
    "unreported_conversions = estimated_conversions - advertiser_conversions. Proxy for 'pending conversions' questions — label it unreported, not a Cake pending queue. Empty/null AM or BD names roll up as (unassigned). sales_rep is not BD; BD is account_sales_manager.",
    "Optional max_advertiser_conversions (>= 0) filters merged rows in process (OD-1 — never SQL HAVING on whale for Admin conversions). Keeps advertiser_conversions <= max; when max is 0 also requires clicks > 0. Does not shrink unbounded summary totals. truncated still means the pre-filter whale row cap."
  ],
  "notes": "clicks come from whale.advertiser_stats_report. The cake UI's optimised path substitutes aff_cpc_summary click counts so its table matches its click-details modal (getAdvertiserStatsOptimized line 338). This tool does not, because that would couple it to OD-2. The response states clicks_source so a discrepancy against the UI is explainable rather than a bug report. Default grain is publisher×advertiser (cake UI table). For a single all-advertisers total, pass group_by=total and read summary.spend — do not SUM truncated rows. For a cross-advertiser affiliate leaderboard, pass group_by=affiliate (do not require advertiser_id; do not N× loop advertisers). For high/low Account Manager this month, pass group_by=account_manager, sort_by=valid_clicks, show_oos=0 unless the user asked for OOS; rank rows[].valid_clicks (affiliate_stats_report, not IVT). For high/low BD manager this month, pass group_by=account_sales_manager (not sales_rep), same sort/show_oos. For 'affiliate with more pending conversions' pass group_by=affiliate, sort_by=unreported_conversions, and label the answer unreported. Prefer this over adhoc_explore; if this tool errors or returns empty rows, fall back to adhoc_explore. For revenue generated by one publisher (e.g. fatcoupon) pass publisher_id or affiliate_id, group_by=total, and read summary.sales_revenue. Do not use this tool to rank campaigns (no camp_id). Advertiser average CPA last 30 vs previous 30 is two calls with group_by=advertiser (rolling last 30 then previous 30); this month vs last month is MTD then last calendar month; read rows[].cpa (cite actual_cpa when it differs; omit when cpa is null; kpi is not dollars; do not blend summary.spend / summary.estimated_conversions); do not use compare_campaign_performance."
}
