{
  "id": "get_publisher_report",
  "mcp_tool": "get_publisher_report",
  "status": "approved",
  "source_system": "cake",
  "environment": [
    "staging",
    "prod"
  ],
  "sharded": true,
  "shard_schema": "lucos_ro_cake",
  "shard_view_prefix": "lucos_ro_blacklist_fraud",
  "connections": {
    "admin": "cake_admin_ro",
    "shorty": "cake_shorty_ro"
  },
  "objects": [
    "Admin.lucos_ro_publisher_daily_stats",
    "Admin.lucos_ro_publisher_clicks",
    "Admin.lucos_ro_affiliate_directory",
    "Admin.lucos_ro_affiliate_cpa_flag",
    "lucos_ro_cake.lucos_ro_blacklist_fraud_*",
    "lucos_ro_cake.lucos_ro_geo_fail_*",
    "lucos_ro_cake.lucos_ro_conv_tracking_*",
    "lucos_ro_members.lucos_ro_cpa_*"
  ],
  "parameters": {
    "publisher": {
      "type": "string",
      "required": false
    },
    "group_by": {
      "type": "enum",
      "required": false,
      "default": "publisher",
      "values": [
        "publisher",
        "day",
        "advertiser",
        "total"
      ]
    },
    "brand": {
      "type": "string",
      "required": false
    },
    "sort_by": {
      "type": "enum",
      "required": false,
      "values": [
        "date",
        "net_revenue",
        "net_clicks",
        "clicks",
        "searches",
        "fraud_clicks",
        "dropped_clicks",
        "conversions",
        "net_cpc"
      ]
    },
    "sort_dir": {
      "type": "enum",
      "required": false,
      "values": [
        "desc",
        "asc"
      ]
    },
    "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": 7,
    "max_rows": 500,
    "timeout_ms": 60000
  },
  "allow_fields": [
    "date",
    "publisher_id",
    "publisher_name",
    "brand",
    "adv_id",
    "adv_name",
    "searches",
    "clicks",
    "net_clicks",
    "click_adjustment",
    "fraud_clicks",
    "dropped_clicks",
    "fraud_pct",
    "conversions",
    "net_revenue",
    "net_cpc",
    "days",
    "source"
  ],
  "deny_fields": [
    "aff_rev_share",
    "rev_share",
    "gross",
    "avgcpc",
    "pass_clr",
    "SSN",
    "Tax_ID",
    "email",
    "paypal_email",
    "telephone",
    "search_terms",
    "subid",
    "click_ip",
    "search_ip"
  ],
  "post_processing": [
    "Mirrors members.admedia.com Reports > Total Revenue / CPC Report (stats_model.php getAccountStats, line 29; the non-ADCPM daily_stats branch at 148-298, the CPA branch at 299-370, and the final clamp at 371). The output must equal what the portal prints, including where the portal's arithmetic is odd; no 'more correct' formula is substituted and no alternative column is offered.",
    "Revenue is read at (publisher, date, feed) and folded in the portal's own row order (group by creation, feed). Per-feed net_revenue is SIGNED and is added to the day (263); if the day's running total goes negative, the day total AND that feed's contribution are both set to 0 (266). The range total accumulates the post-clamp contributions (281) and is clamped once at the end, per publisher (371).",
    "The day total and the range total are accumulated separately and can legitimately disagree; neither is derived from the other. group_by=day rows carry the day figure; group_by=publisher/total rows and summary.net_revenue carry the range total.",
    "fraud_clicks = SUM(click_fraud + click_adjustment) over feeds (242) PLUS Amazon blacklist fraud added once per date (221-225). dropped_clicks = SUM(click_dropped) PLUS geo-fail drops added once per date (227-231). Both additions are cancelled back out of net clicks at 245, so net_clicks and net_revenue are unaffected by them.",
    "conversions come from conv_tracking, NOT from daily_stats.conv (264 is commented out; 265 assigns getEstimateConversion). Per date: DISTINCT camp_id from aff_cpc_summary for that publisher with status = 1 (2558-2560), then the conversion count of those campaigns that day (2571). That count is NOT scoped to the publisher inside conv_tracking, so a shared campaign contributes its whole count to each publisher - a portal quirk, reproduced. An empty campaign list is 0 (2567).",
    "There is NO aff_cpc_summary fallback for recent days. getAccountStats reads daily_stats only, so a day the daily aggregate has not built yet is simply absent, exactly as it is on the portal.",
    "group_by=advertiser has NO portal equivalent: daily_stats carries no advertiser dimension. That grain reads Admin.lucos_ro_publisher_clicks (aff_cpc_summary) and reports clicks and earnings only, marked source='aff_cpc_summary'.",
    "net_cpc is net_revenue / net_clicks derived after summing, never averaged across feed rows, and is 0 rather than null when there are no net clicks (254, 256). net_revenue and net_cpc are rounded to 2dp, as the portal displays them.",
    "rev_share and gross revenue are never exposed: gross/net would reveal the rev share, so the view publishes net only.",
    "CPA BRANCH (299-370): a publisher with Affiliate.cpa = 1 goes through getAccountStats' SECOND branch, on top of the first. CPA-ness is resolved by NAME through Admin.lucos_ro_affiliate_cpa_flag (AffiliateIDs JOIN Affiliate, 301-306) and the portal requires EXACTLY ONE matching row (303), so a name carrying two AffiliateIDs rows is not CPA. The id that comes back is the one in the table name, cpa_aff_<id>_<YYYYMM>.",
    "CPA BRANCH: searches, fraud_clicks and dropped_clicks keep their daily_stats values (309-311 for the totals; the per-day values are simply never touched). clicks, net_revenue and conversions are REPLACED per date from cpa_aff_<id>_<YYYYMM> on the keywords connection - clicks = SUM(clicks), net_revenue = SUM(aff_cost), conversions = SUM(orders) (343-345) - and their range totals restart from ZERO (314-317) and take only what those rows carry (351-354). The daily_stats values for those three are discarded. Read through lucos_ro_members.lucos_ro_cpa_YYYYMM, the same generated monthly view get_cpa_publisher_report uses, because the per-publisher tables have no publisher column.",
    "CPA BRANCH: the portal has ONE clicks column for a CPA publisher (Valid Clicks). This tool has clicks and net_clicks, so BOTH are set to the CPA clicks figure and the note says so - they are not two different portal numbers. fraud_pct is not a portal column and for a CPA publisher divides a daily_stats numerator by CPA clicks.",
    "CPA BRANCH: a date daily_stats has but the CPA tables do not is never visited by the loop at 332-356, so its group_by=day row keeps the daily_stats clicks/revenue/conversions while contributing NOTHING to the range totals; the day rows and summary disagree there and the portal prints both. A date only the CPA tables have gets a new daily entry with cc/rev/conv and no sc/fc/dc (343-345), so it reads as zero searches, fraud and dropped. adv_conv (347) is not emitted.",
    "CPA BRANCH: a month whose lucos_ro_cpa_YYYYMM view does not exist contributes nothing rather than erroring, as line 326 skips a missing table; those months are listed in shard_coverage.cpa_months.missing as YYYYMM. Rows sourced this way are marked source='daily_stats+cpa_aff'. Non-CPA publishers are untouched by all of this - same queries, same numbers - and the keywords connection is not read at all when no CPA publisher is in scope.",
    "A day with no lucos_ro_blacklist_fraud_/lucos_ro_geo_fail_/lucos_ro_conv_tracking_ view counts 0 and is not an error, matching the portal's own missing-table branches (407-409, 449-451). Those days are listed in shard_coverage so a reader can tell 'none happened' from 'we could not look'.",
    "summary is computed over every matching row before the limit, so totals stay correct when rows are capped."
  ],
  "notes": "Publisher (members portal) performance: searches, clicks, net clicks, fraud/dropped, conversions, net revenue and net CPC, reproduced to match members.admedia.com exactly. publisher accepts a name or a numeric id; omit it to report across all publishers. Reads the Admin RO views, three day-sharded lucos_ro_cake views on the Shorty RO connection (blacklist fraud, geo-fail drops, conv_tracking), and - for CPA publishers only - the monthly lucos_ro_members.lucos_ro_cpa_YYYYMM views on the keywords RO connection. Distinct from get_affiliate_report, which reports cake whale/Admin publisher earnings rather than the members portal figures, and from get_cpa_publisher_report, which is the portal's separate per-campaign CPA report."
}
