{
  "id": "get_campaign_affiliate_report",
  "mcp_tool": "get_campaign_affiliate_report",
  "status": "approved",
  "source_system": "cake",
  "environment": ["staging", "prod"],
  "sharded": false,
  "connections": {
    "admin": "cake_admin_ro"
  },
  "objects": [
    "Admin.lucos_ro_affiliate_cpc_summary_daily",
    "Admin.lucos_ro_campaign_directory",
    "Admin.lucos_ro_affiliate_directory"
  ],
  "parameters": {
    "affiliate_id": { "type": "int", "required": false },
    "publisher_id": { "type": "string", "required": false },
    "advertiser_id": { "type": "int", "required": false },
    "campaign_id": { "type": "int", "required": false },
    "active_only": { "type": "int", "required": false, "default": 0 },
    "date_from": { "type": "date", "required": false },
    "date_to": { "type": "date", "required": false },
    "limit": { "type": "int", "required": false, "default": 50 }
  },
  "constraints": {
    "max_window_days": 92,
    "default_window_days": 31,
    "max_rows": 500,
    "timeout_ms": 60000
  },
  "allow_fields": [
    "affiliate_id",
    "affiliate_name",
    "camp_id",
    "campaign_name",
    "campaign_status",
    "adv_id",
    "adv_name",
    "clicks",
    "affiliate_earning"
  ],
  "deny_fields": ["rev_share", "net_cpc", "cpc", "click_table_id", "sales_revenue"],
  "post_processing": [
    "Aggregate CPC first by (affiliate_id, affiliate_name, camp_id, adv_id, adv_name): SUM(clicks), SUM(affiliate_earning). Then join lucos_ro_campaign_directory for campaign_name and status.",
    "Optional filters: affiliate_id or publisher_id (Affiliate.name, e.g. fatcoupon / rateit_shop), advertiser_id (adv_id), campaign_id (camp_id). active_only 0/1, default 0; when 1 keep d.status = 'A' (MNGT UI Active). Unscoped is allowed and row-capped (unlike get_affiliate_report, which requires an id).",
    "Association is CPC traffic, not Admin.affiliate_offers_mappings. Assigned live offers are list_publisher_offers.",
    "Do not output publisher_id. Publisher identity on this grain is affiliate_id / affiliate_name.",
    "Do not invent sales_revenue at this grain. Traffic/campaign-responsible answers use clicks; advertiser revenue stays on campaign or affiliate reports. Do NOT invent a new view."
  ],
  "notes": "Campaign × affiliate CPC grain (TW-341). Cake has no sales_revenue at (affiliate_id, camp_id). Assigned live offers are list_publisher_offers (AffiliateIDs → mappings → live offers). get_advertiser_report affiliate grain has no camp_id; campaign report has no affiliate_id. CPC view already has (affiliate_id, camp_id, adv_id). Do not name-parse."
}
