Grin model

Campaign performance

One row per campaign: the creator funnel, the content it produced, the sales it drove and what it cost. Grin's campaign report, with the costs on the same row.

Grain
one row per grin_account x campaign
Built from
  1. 11
    Raw tables
  2. 11
    Staging
  3. 2
    Core
  4. 1
    Analytics
Reads from Grin
campaigncampaign_conversioncampaign_payablecontactcontact_ordercontentcontent_statproduct+3 more
Ships as
Weld and dbt SQL, open source on GitHub
core.grin.campaign_performance
1-- core.grin.campaign_performance
2-- One row per campaign: the creator funnel, the content it produced, the sales it
3-- drove and what it cost. Grin's campaign report, with the costs on the same row.
4--
5-- Grain: one row per grin_account x campaign.
6-- Depends on: staging.grin.campaign, .campaign_conversion,
7--             core.grin.creator_campaign_performance
8--
9-- Rolled up from creator_campaign_performance, so the two always agree; every
10-- campaign is kept, including ones with no activity yet.
11--
12-- RATES ARE RATIOS OF SUMS: engagement rate is total engagements over total reach,
13-- not the average of each post's rate, so one viral post on a small account cannot
14-- dominate the campaign figure.
15--
16-- RETURN IS ONLY COMPUTED WHEN THE CURRENCIES MATCH. Revenue is in the store's
17-- currency, payables in US dollars. revenue_per_payable_usd is filled only when the
18-- campaign's conversions are all in USD. Seeding cost is in the currency product
19-- costs were entered in and is left out of the ratio; add it yourself once you
20-- know which currency that is.
21
22WITH campaign_rollup AS (
23    SELECT
24        grin_account,
25        campaign_id,
26        COUNT(*)                                   AS creators,
27        COUNTIF(proposals > 0)                     AS creators_proposed,
28        COUNTIF(is_accepted)                       AS creators_accepted,
29        COUNTIF(is_declined AND NOT is_accepted)   AS creators_declined,
30        COUNTIF(is_completed)                      AS creators_completed,
31        COUNTIF(posts > 0)                         AS creators_posted,
32        COUNTIF(conversions > 0)                   AS creators_with_sales,
33        SUM(agreed_fee)                            AS agreed_fees,
34        SUM(posts)                                 AS posts,
35        SUM(stories)                               AS stories,
36        SUM(impressions)                           AS impressions,
37        SUM(reach)                                 AS reach,
38        SUM(views)                                 AS views,
39        SUM(engagements)                           AS engagements,
40        SUM(imv)                                   AS imv,

This model and the 13 it depends on. Deploy them together; refs like {{staging.…}} resolve to the matching folders in Weld, and the dbt view uses source() and ref().

Notes

How it counts. And where it stops.

Rolled up from creator_campaign_performance, so the two always agree; every campaign is kept, including ones with no activity yet. RATES ARE RATIOS OF SUMS: engagement rate is total engagements over total reach, not the average of each post's rate, so one viral post on a small account cannot dominate the campaign figure. RETURN IS ONLY COMPUTED WHEN THE CURRENCIES MATCH. Revenue is in the store's currency, payables in US dollars. revenue_per_payable_usd is filled only when the campaign's conversions are all in USD. Seeding cost is in the currency product costs were entered in and is left out of the ratio; add it yourself once you know which currency that is.

Raw tables it reads

  • raw.grin.campaign
  • raw.grin.campaign_conversion
  • raw.grin.campaign_payable
  • raw.grin.contact
  • raw.grin.contact_order
  • raw.grin.content
  • raw.grin.content_stat
  • raw.grin.product
  • raw.grin.product_selection_item
  • raw.grin.product_variant
  • raw.grin.proposal

Put Grin in front
of your agents.

Connect Grin, deploy the models, and ask questions in Claude, Cursor or ChatGPT. Free for 14 days, no credit card.