Marketing Leadgen
Campaigns anchor a B2B demand-generation business, tying ad spend, web traffic and attribution to leads that move through pipeline stages to a closed deal, with accounts carrying the firmographics ABM depends on.
Marketing reports leads, sales reports closed deals, and nobody can trace a campaign's spend to the pipeline it created.
Paste this into your AI assistant. It builds the Marketing Leadgen model's Data Marts in your data warehouse from your ad, web and CRM data, and shows which campaigns create pipeline and revenue once spend is counted.
The prompt
Read https://www.owox.com/ai-agent-setup.md and follow it to set up OWOX Data Marts for me, starting from /data-models/marketing-leadgen.md. The task: build the Marketing Leadgen model with its Campaign, Touchpoints and Opportunities Data Marts, then show me which channels and campaigns create the most pipeline and revenue once ad spend and attribution credit are counted. Tag the links you give me with utm_campaign=ai-task and utm_term=m1. Use what you know about my context and goals
A B2B demand-generation and revenue-operations business modeled end to end — from ad spend and marketing campaigns, through web traffic and multi-touch attribution, to leads, sales opportunities and the pipeline stages that carry them to close. Campaigns anchor everything, paid and non-paid alike: every dollar of ad spend, every paid visit and every touch on the path to conversion ties back to a campaign, so channel performance can be traced from a click to a closed-won deal. Deals are won or lost at the account level, so Account carries the firmographics and the ABM target-list flag.
How this business is managed
- Marketing — turns paid and organic traffic into marketing-qualified leads at a cost the business can sustain.
- Sales — converts qualified leads into closed-won revenue, focused on the accounts most likely to buy.
- Finance — makes sure ad spend is matched by the pipeline and revenue the business brings in.
Key metrics
- MQLs — Lead · MQLs by Lead · Became MQL At.
- Ad spend — Ad Spend · Cost.
- Pipeline — Opportunities · Amount by Opportunities · Created At; per $1 of spend, Campaign Performance · Pipeline per $ of Spend.
- Closed-won revenue — Opportunities · Closed-Won Revenue by Opportunities · Close Date.
Key Data Marts
- Lead — every contact, with the dated milestones of its way through the funnel.
- Opportunities — the deals, their size and their outcome.
- Ad Spend — what each channel and campaign costs, day by day.
- Touchpoints — the multi-touch credit behind every lead and deal.
Example Questions
- Which channels and
campaignsgenerate the most pipeline and revenue oncead spend, attribution credit and win rates are accounted for? - Where does the funnel leak most — awareness to
lead, lead to MQL/SQL, oropportunityto close — and does that differ by channel? - Do target (ABM)
accountsconvert, close and win at a higher rate than the rest of the book?
To connect live data to your data model, plug in the data sources you need
Account
One row per target company, the firmographic backbone the rest of the model hangs off. Every account carries an industry, an employee-count band and a region, plus a flag marking whether it sits on the account-based marketing (ABM) target list. Because real B2B deals belong to accounts rather than to whichever individual filled out a form, this mart is the join point that lets leads, opportunities and touchpoints be rolled up to "who is this company, and how big an opportunity is it."
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
account_id | STRING | Account ID | PK. Unique identifier for the target company. |
name | STRING | Name | Company name. |
industry | STRING | Industry | Industry the company operates in. |
employee_band | STRING | Employee Band | Company-size bucket — the mid-market segmentation axis. Vocabulary: 1-50 / 51-200 / 201-1000 / 1000+. |
region | STRING | Region | Geographic region of the company. |
is_target_account | BOOLEAN | Is Target Account | Whether the company is on the ABM target list. ~10–20% of accounts, skewed toward larger employee_band; target accounts show higher engagement, pipeline and win rate. |
Ad Spend
Daily cost, impressions and clicks for every paid campaign, broken out by ad group. This is the cost side of the funnel — the numbers a demand-gen team reconciles against Google Ads, LinkedIn Ads and other platform reports every week to know what a click, a lead and ultimately a closed deal actually costs.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
spend_id | STRING | Spend ID | PK. Unique identifier for each spend record. |
spend_date | DATE | Spend Date | Day the spend was incurred. Within the campaign's [start_date, end_date] flight. |
campaign_id | STRING | Campaign ID | Campaign this spend belongs to. FK to Campaign |
channel | STRING | Channel | Marketing channel where the cost was spent. Denormalized copy of the campaign's channel. |
ad_group | STRING | Ad Group | Ad group or ad set within the campaign. |
impressions | INTEGER | Impressions | Number of times ads were shown. |
clicks | INTEGER | Clicks | Number of clicks the ads received. Always ≤ impressions; CTR by channel. |
cost | NUMERIC | Cost | Money spent on this ad group for the day (USD). Derived from clicks × per-channel CPC. |
cpc | FLOAT | CPC | Calculated Field. Cost per click, USD: summed cost over summed clicks, right at every cut — channel, campaign, ad group or week. Averaging a per-row CPC would weight a quiet day like a busy one and drop the days with no clicks. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Campaign | campaign_id = campaign_id | N:1 | The campaign this spend was bought for. |
Campaign
Reference of every marketing initiative run — paid and non-paid alike, from search and social ads to email nurtures, webinars and content syndication — each carrying its channel, its objective and the UTM tags that tie it back to ad-platform and web-analytics data. This is the conformed campaign dimension of the model: every channel value that shows up on ad spend, web sessions, touchpoints or opportunities agrees with what is declared here, so a channel-level report never splits into phantom categories.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
campaign_id | STRING | Campaign ID | PK. Unique identifier for each campaign. |
campaign_name | STRING | Campaign Name | Human-readable name of the campaign. |
channel | STRING | Channel | Marketing channel. Controlled vocabulary: paid_search, paid_social, display, organic_search, email, webinar, content_syndication, direct, referral. |
objective | STRING | Objective | Primary goal. One of: awareness, lead_generation, conversion, retargeting. |
utm_source | STRING | UTM Source | UTM source tag identifying where the traffic originates. |
utm_medium | STRING | UTM Medium | UTM medium tag describing the type of traffic (e.g. cpc, email). |
utm_campaign | STRING | UTM Campaign | UTM campaign tag — the key ad-platform ↔ web-analytics join key. |
start_date | DATE | Start Date | Date the campaign went live. |
end_date | DATE | End Date | Date the campaign flight ended (nullable for always-on). Spend/touches fall within [start_date, end_date]. |
Campaign Performance Data Mart
Campaign Performance
One row per campaign per day, putting what the campaign cost next to what it produced: ad spend, impressions and clicks from Ad Spend, leads created from Touchpoints, and opportunities, pipeline and closed-won revenue from Opportunities. A row exists for every day the campaign spent money or produced a lead, an opportunity or a won deal, so leads from organic campaigns and from days with no spend are all here. This is the mart for cost per lead, cost per opportunity and pipeline per dollar of spend — one number at any date grain, campaign or channel. Each result is counted on its own day: spend on the day it was spent, a lead on the day it was created, an opportunity on the day it opened, revenue on the day the deal closed.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
campaign_id | STRING | Campaign ID | PK. Composite key together with activity_date. Campaign these daily totals belong to. FK to Campaign |
activity_date | DATE | Activity Date | PK. Composite key together with campaign_id. Day the spend, leads, opportunities or won deals on this row happened. |
channel | STRING | Channel | Marketing channel of the campaign, copied from Campaign.channel. Only paid_search, paid_social and display carry spend. |
cost | NUMERIC | Cost | Ad spend on the campaign that day (USD), summed over its ad groups; 0 on days with no spend and for campaigns that never buy ads. |
impressions | INTEGER | Impressions | Ad impressions the campaign bought that day; 0 without spend. |
clicks | INTEGER | Clicks | Ad clicks the campaign received that day; 0 without spend. |
leads_created | INTEGER | Leads Created | Leads the campaign created that day, counted on the lead's creation date. Each lead counts once, under the campaign of its lead-creating touch. |
opportunities_created | INTEGER | Opportunities Created | Sales opportunities opened that day with this campaign as their primary campaign source. |
pipeline_amount | NUMERIC | Pipeline Created | Deal amount (USD) of the opportunities opened that day, whatever their outcome. |
deals_won | INTEGER | Deals Won | Opportunities from this campaign won that day, counted on their close date. |
won_revenue | NUMERIC | Closed-Won Revenue | Deal amount (USD) of the opportunities from this campaign won that day, counted on their close date. |
cost_per_lead | FLOAT | Cost per Lead | Calculated Field. Ad spend per lead created, USD: summed cost over summed leads, right at any date grain, campaign or channel. A lead counts on the day it was created. Only paid search, paid social and display carry spend, so an organic channel reads $0 and display, with no leads, reads empty; across all channels it divides paid spend by every lead — filter to the paid channels for a paid cost per lead. |
cost_per_opportunity | FLOAT | Cost per Opportunity | Calculated Field. Ad spend per opportunity opened, USD: summed cost over summed opportunities, right at any date grain, campaign or channel. An opportunity counts on the day it opened, under its primary campaign source. Organic channels carry no spend and read $0; across all channels it divides paid spend by every opportunity, organic-sourced included. |
pipeline_per_dollar | FLOAT | Pipeline per $ of Spend | Calculated Field. Pipeline created per $1 of ad spend: summed deal amount of opportunities opened over summed cost. Pipeline counts on the day the opportunity opened, whatever its outcome. Organic channels have pipeline but no spend and read empty; across all channels it credits organic pipeline to paid spend. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Campaign | campaign_id = campaign_id | N:1 | The campaign these daily totals belong to. |
Lead
One row per lead or contact captured by marketing, carrying where they came from, their fit-and-engagement score, the dated milestones marking when they became marketing-qualified (MQL) and sales-qualified (SQL), and firmographics that mirror the account they belong to. Because the qualification milestones are dated rather than just a current-state label, leads can be grouped into cohorts — "leads created in March" — and tracked through the funnel over time, not just counted at a single point.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
lead_id | STRING | Lead ID | PK. Unique identifier for each lead or contact. |
account_id | STRING | Account ID | Company the lead belongs to; null for a lead not yet matched to an account. FK to Account |
created_at | TIMESTAMP | Created At | When the lead first entered the system (coincides with the is_lead_create touchpoint). |
source_channel | STRING | Source Channel | Channel that first brought in the lead. Equals the channel of the lead's is_lead_create touchpoint. Same controlled vocabulary as Campaign.channel. |
lead_score | INTEGER | Lead Score | Fit + engagement score for MQL gating. |
became_mql_at | TIMESTAMP | Became MQL At | When the lead reached marketing-qualified status; null if never MQL. created_at ≤ became_mql_at. |
became_sql_at | TIMESTAMP | Became SQL At | When the lead reached sales-qualified status; null if never SQL. became_mql_at ≤ became_sql_at. |
employee_band | STRING | Employee Band | Company-size bucket. Same vocabulary as Account.employee_band: 1-50 / 51-200 / 201-1000 / 1000+. For matched leads, agrees with the account. |
industry | STRING | Industry | Industry the lead's company operates in. For matched leads, agrees with the account. |
country | STRING | Country | Country where the lead is located; consistent with the account's region. |
lifecycle_stage | STRING | Lifecycle Stage | subscriber / lead / MQL / SQL / opportunity / customer. Consistent with the became_*_at timestamps and with the existence of an Opportunities row. |
mqls | INTEGER | MQLs | Calculated Field. Leads that reached marketing-qualified status, counted once each. Cut by Became MQL At for MQLs per month; by Created At to follow a lead cohort. |
sqls | INTEGER | SQLs | Calculated Field. Leads that reached sales-qualified status, counted once each; every SQL was an MQL first. Cut by Became SQL At for SQLs per month. |
lead_to_mql_rate | FLOAT | Lead-to-MQL Rate | Calculated Field. Share of leads that became MQLs, per lead created. Cut by Created At, never by Became MQL At (that keeps only MQLs and reads 100%). Leads of the last two months are still qualifying. |
mql_to_sql_rate | FLOAT | MQL-to-SQL Rate | Calculated Field. Share of MQLs that became SQLs, per MQL. Reads MQLs and SQLs, so the three always agree. Cut by Became MQL At or Created At; MQLs of the last two months are still being worked. |
sql_to_opp_rate | FLOAT | SQL-to-Opportunity Rate | Calculated Field. Share of SQLs that became opportunities, per SQL; a lead at opportunity or customer stage counts. Reads SQLs. Cut by Became SQL At or Created At; SQLs of the last month are still converting. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Account | account_id = account_id | N:1 | The company the lead works for. |
Opportunities
One row per sales opportunity — the deal object, keyed to the account rather than a single lead, since real B2B buying committees involve multiple people. Each row carries the pipeline stage, the deal size (ACV), the sourcing lead and campaign, and the win/loss outcome with close date and sales-cycle length. Each deal's path through the stages is recorded in Stage Transitions. This is where marketing-sourced pipeline turns into — or fails to turn into — closed revenue.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
opportunity_id | STRING | Opportunity ID | PK. Unique identifier for each sales opportunity. |
account_id | STRING | Account ID | Account the deal belongs to — the primary spine. FK to Account |
lead_id | STRING | Lead ID | Sourcing/converting lead the opportunity originated from; nullable. FK to Lead |
primary_campaign_id | STRING | Primary Campaign ID | Primary Campaign Source (single-touch sourcing); nullable. Equals the campaign of the sourcing lead's is_lead_create touchpoint. FK to Campaign |
created_at | TIMESTAMP | Created At | When the opportunity was created. |
stage | STRING | Stage | Current pipeline stage. One of: discovery, demo, proposal, negotiation, closed_won, closed_lost. |
amount | NUMERIC | Amount | ACV / deal size (USD). Correlates with the account's employee_band. |
close_date | DATE | Close Date | Date the opportunity was won or lost; null while open. |
is_won | BOOLEAN | Is Won | True iff stage = closed_won (then close_date is set); false with a close_date means closed_lost. |
sales_cycle_days | INTEGER | Sales Cycle Days | DATE_DIFF(close_date, created_at, DAY) for closed deals; null while open. |
owner | STRING | Owner | Sales rep who owns the opportunity (drawn from a small stable roster). |
opp_to_won_rate | FLOAT | Opp-to-Won Rate | Calculated Field. Share of opportunities won, per opportunity created — open deals stay in the base. Cut by Created At for a cohort. About half of every cohort, old or new, is still open, so even old cohorts never mature. Not won ÷ closed. |
won_revenue | NUMERIC | Closed-Won Revenue | Calculated Field. Deal amount on won opportunities, USD, summed; open and lost deals add nothing. Cut by Close Date for bookings by month or quarter. |
deal_size_band | STRING | Deal Size Band | Calculated Field. Small (<$15K), Mid ($15-50K), Large ($50-100K) or Enterprise ($100K+), by deal amount. A row-level formula that behaves as a dimension: group and filter by it. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Account | account_id = account_id | N:1 | The company the deal is with. |
| Campaign | primary_campaign_id = campaign_id | N:1 | The campaign credited with sourcing the deal. |
| Lead | lead_id = lead_id | N:1 | The lead the deal grew out of. |
Stage Transitions
One row per pipeline stage change for a sales opportunity, tracing each deal's path through discovery, demo, proposal and negotiation on the way to a win or a loss. The time spent in each stage before moving to the next is the raw material for bottleneck analysis — where deals get stuck, and for how long.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
transition_id | STRING | Transition ID | PK. Unique identifier for the stage change. |
opportunity_id | STRING | Opportunity ID | Opportunity that moved stage. FK to Opportunities |
from_stage | STRING | From Stage | Stage the opportunity left. |
to_stage | STRING | To Stage | Stage the opportunity entered. Contiguous chain: to_stage of row n = from_stage of row n+1; the last to_stage equals Opportunities.stage. |
transitioned_at | TIMESTAMP | Transitioned At | When the stage change happened. Within [Opportunities.created_at, close_date]. |
days_in_from_stage | INTEGER | Days In From Stage | Days spent in the previous stage — the velocity bottleneck driver. Sums (per opportunity) to the sales cycle for closed deals. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Opportunities | opportunity_id = opportunity_id | N:1 | The deal that moved stage. |
Touchpoints
One row per marketing touch a lead has on the way to becoming a customer, credited under a W-shaped multi-touch attribution model that splits credit 30% to the first touch, 30% to lead creation, 30% to opportunity creation and 10% across everything in between. This is the mart that answers "which channels and campaigns actually deserve credit," rather than crediting only the first click or the last one. Multiplied by the deal size in Opportunities, that credit becomes each channel's share of pipeline.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
touchpoint_id | STRING | Touchpoint ID | PK. Unique identifier for each marketing touch. |
lead_id | STRING | Lead ID | Lead that this touch belongs to. FK to Lead |
campaign_id | STRING | Campaign ID | Campaign associated with this touch. FK to Campaign |
occurred_at | TIMESTAMP | Occurred At | When the touch happened. |
channel | STRING | Channel | Channel where the touch occurred. Equals the campaign's channel; same controlled vocabulary as Campaign.channel. |
touch_type | STRING | Touch Type | Kind of interaction. One of: ad_click, form_fill, email_open, email_click, webinar_attend, content_download, demo_request. Consistent with channel (e.g. no email_open on paid_search). |
touch_credit | FLOAT | Touch Credit | W-shaped credit for this touch. Sums to exactly 1.0 per lead: 30% first touch, 30% lead creation, 30% opportunity creation, 10% across middle touches. |
is_first_touch | BOOLEAN | Is First Touch | True on the lead's earliest touch (exactly one per lead). W-shaped anchor. |
is_lead_create | BOOLEAN | Is Lead Create | True on the touch that created the lead (exactly one per lead; coincides with Lead.created_at). W-shaped anchor. |
is_opp_create | BOOLEAN | Is Opp Create | True on the touch that created the opportunity (at most one per lead; only for leads with an opportunity; coincides with Opportunities.created_at). W-shaped anchor. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Campaign | campaign_id = campaign_id | N:1 | The campaign that produced this touch. |
| Lead | lead_id = lead_id | N:1 | The lead who was touched. |
Web Sessions
One row per web session, the very top of the funnel, before most visitors are ever identified as a lead. Each session records the traffic source and medium, the landing page, whether it converted into a form submission, and — for the small share of visitors identified through identity stitching — the lead that session belongs to. Because the overwhelming majority of B2B web traffic never converts or gets identified, most sessions carry no lead at all; the ones that do show exactly where anonymous traffic turns into a known contact.
Fields
| Column | Type | Alias | Description |
|---|---|---|---|
session_id | STRING | Session ID | PK. Unique identifier for the web session. |
lead_id | STRING | Lead ID | Known lead after identity stitching; null for never-identified visitors (the overwhelming majority). FK to Lead |
started_at | TIMESTAMP | Started At | When the session began. |
campaign_id | STRING | Campaign ID | Campaign that drove the session; null for organic/direct. FK to Campaign |
source | STRING | Source | Traffic source that referred the session; consistent with the driving campaign's utm_source. |
medium | STRING | Medium | Marketing medium (e.g. organic, cpc, email); consistent with the driving campaign's utm_medium. |
landing_page | STRING | Landing Page | First page viewed in the session. |
form_submits | INTEGER | Form Submits | Number of forms submitted during the session. ≥ 1 when is_conversion is true. |
is_conversion | BOOLEAN | Is Conversion | Whether the session produced a lead or demo request. Session→lead conversion ~1–3% overall, lowest on paid social. |
Relationships
| Related data mart | On | Cardinality | Meaning |
|---|---|---|---|
| Campaign | campaign_id = campaign_id | N:1 | The campaign that drove the visit. |
| Lead | lead_id = lead_id | N:1 | The lead this visit was later tied to. |
- 1
Install the Import Model plugin
One plugin, installed once, in your own OWOX workspace.
- 2
Import this model
Point it at this bundle and it creates every data mart above, joins and all.
- 3
Plug in your data and destinations
Connect your own sources and send the results where your team already works.
4. Optional — customize as you wish. Rename a column, drop a mart, add your own: once it is imported it is yours, and nothing here syncs back.
You are booked. The invitation is on its way to , with the calendar invite and the meeting link.