AI Pillar Campaign Performance — wireframe for review · connecting…
Reviewing as
Bentley

AI Pillar Overview

2026 campaign · visibility → engagement → progression → commercial impact

Selected Date Range:Q3 2026 · 12 weeks
AI Pillar Overview
Campaign performance reporting with traceable KPIs, sourced from the AI Pillar data layer
SAMPLE DATA · NOT LIVE
Executive Performance Eight headline KPIs · click a metric for its lineage
Data note: All displayed performance figures and comparisons are illustrative; actual production values require validated source connections.

Campaign Performance Trend

Weekly engaged known contacts and campaign MQLs · source-normalized and deduplicated

Engaged known contactsCampaign MQLs

Audience Journey

Distinct identified people, then qualified downstream stages

The progression numerator requires a later action after the initial qualified engagement.

Engaged Audience by Channel

Unique known contacts, assigned to a first qualifying engagement source

Visibility vs. Response

Different units shown separately — not summed

Leadership Interpretation

What the executive page is designed to answer

1 · Are we visible?Reach, SOV, discoverability
2 · Are we resonating?Engaged contacts, actions
3 · Are we advancing?Progression, MQLs
4 · Is it translating to demand?Intent, influenced pipeline
eStore orders and revenue remain separate commercial outcomes rather than being added to qualified intent.

Event impact · who was in the room from Dallas cohort analysis

Turnout, the accounts and ARR in the room, and the pipeline that was already open at those accounts on the day · one row per AI Pillar event

EventRegisteredAttendedExternal show rateAccounts in the roomARR in the roomOpen pipeline in the room illustrativePipeline created ≤90 days after illustrative
Illuminate Dallas 202693657054%140 of 202$302M$48.6M · 212 opps$6.1M · 37 opps
Next AI Pillar eventsPopulate automatically once each event campaign sits under an AI Pillar sub-theme in Salesforce
Open pipeline in the room = opportunities at attending accounts (ultimate parent) created before the event date and still open on it. Event Performance →
Visibility & Reach People-based reach separated from exposure and traffic

Unique Identifiable Reach by Channel

De-duplicated contact IDs; excludes anonymous pageviews and impressions

Share of Voice Trend

Bentley AI-term mentions / Bentley plus monitored competitor mentions

Web Discoverability

GA4 AI Hub and AI blog activity (not unique people)

LLM Discoverability

AI mentions, citations and referrals as distinct components

PR / Analyst Context

Visibility diagnostics only; never part of unique-person reach

Content & Audience Engagement

Content Engagement Actions by Channel

Interaction count, including anonymous social interactions · separate from known contact counts

Qualified Engagement Over Time

Content actions versus distinct engaged contacts (two different measures)

Content / Experience Performance

Illustrative assets and event themes across linked touchpoints

Email & Event Diagnostics

Operational metrics feeding the journey without inflating the executive reach count

Audience Progression & Marketing Qualification

Reached → Engaged → Progressed → MQL

Contact-level sequence; MQLs deduplicated across Salesforce AI Pillar subcampaigns

Progression by First Engagement Source

Progressed contacts / engaged contacts in each source

Journey Conversion

Conversion rates are people-based, not counts of repeated activities

Required Business Rules

Proposed data-layer logic applied in this demo

Identity resolutionContact/Lead master ID
Progression sequenceLater action only
Attribution window (demo)90 days
MQL program roll-upAI Pillar parent campaign
Internal AAG contactsExcluded from external MQL
Commercial Impact

Qualified Intent by Request Type

Distinct people in the headline KPI; requests can appear in multiple diagnostic types

Influenced Pipeline by Opportunity Stage

One opportunity counted once after an eligible prior campaign touch

Example Salesforce Opportunities

Synthetic opportunity rows and campaign-influence links; dollar values are not actual pipeline

eStore / Completed Commercial Results

Separate from requests, campaign influence, and pipeline

Influenced pipeline is a strategic relationship indicator, not proof that the campaign solely created the opportunity.
Event Performance Who registered, who showed up, which accounts and how much ARR and open pipeline were in the room
Registered
936
754 external · 182 Bentley
Attended
570
61% overall show rate
External show rate
54%
408 of 754 · ref: Sydney 53%, Berlin 56%
Bentley show rate
89%
162 of 182 · kept separate from external
Accounts in the room
140
of 202 registered companies (69%)
ARR in the room
$302M
global relationship ARR, existing customers
Open pipeline in the roomillustrative
$48.6M
212 open opps at attending accounts on the day
Pipeline created ≤90 days afterillustrative
$6.1M
37 new opps at attending accounts

Who showed up, and who didn’t from Dallas cohort analysis

Registered = attended + no-show · Bentley staff reported apart from external

CohortRegisteredAttendedNo-showShow rateShare of no-shows
External75440834654%95%
Bentley1821622089%5%
Total93657036661%100%
External
408 attended346 no-show
Bentley
162 attended20

What drives no-shows? from Dallas cohort analysis

External registrants · show rate by registrant profile (average 54%; orange = below average)

Registrant profileRegisteredShow rate
Based outside the US72
17%
Registered with a personal email82
23%
Account unknown or <$50K ARR273
36%
Small segment account59
39%
Only person from their company173
39%
Lead (new prospect), not Contact283
44%
Company sent a team of 2–3184
70%
Account with $250K–$1M ARR169
76%
So whatCompanies sending 2–3 people and US registrants showed best; international, personal-email and solo registrants mostly didn’t. Plan on a ~53% external show rate and have account owners reconfirm priority registrants two weeks out.

Which kinds of accounts were in the room from Dallas cohort analysis

Industry derived from account name · 97% US · 75% Enterprise · 69% existing customer contacts

IndustryRegisteredAttendedShow rate
Engineering & design firms466271
58%
Construction & contractors5232
62%
Technology, industrial & other6731
46%
Utilities & energy3020
67%
Government & public agencies2614
54%
Rail, transit & ports1612
75%
Education159
60%
Unknown (personal email)8219
23%

Account turnout and ARR from Dallas cohort analysis

Accounts grouped by how many of their registrants came · ARR on the ultimate-parent account

Account turnoutAccountsAttendedARR (est.)Share of ARR
Full: everyone came83153 of 153$89.8M25%
Partial: some no-shows57189 of 338$212.0M60%
None: no one came620 of 78$51.7M15%
Total registered202342 of 569$353.6M100%
So whatPartial-turnout accounts hold most of the ARR (57 accounts, ~$212M, 149 no-shows). Biggest gaps: WSP 2 of 17, HDR 3 of 11, CDM Smith 4 of 11, AECOM 6 of 15. The opportunity is lifting attendance inside high-value Enterprise accounts, not just registrations.

Open pipeline in the room, by stage illustrative

Opportunities created before the event date and still open on it · attending accounts vs registered no-show accounts

Accounts that attendedRegistered, no one came

Top accounts in the room

Ranked by attendees · ARR from the Dallas analysis; open pipeline illustrative until the query runs

AccountIndustryARR (est.)Attended / reg.Show rateOpen pipeline illustrative
KiewitConstruction$4.6M10 / 1191%$3.2M
Lina T. Ramey & Assoc.Engineering$348K10 / 1471%$0.4M
BNSF RailwayRail & transit$267K10 / 10100%$0.9M
FerrovialConstruction$1.0M9 / 2045%$1.6M
Black & VeatchEngineering$7.9M8 / 1267%$4.1M
AtkinsRéalisEngineering$11.0M8 / 1553%$5.3M
STVEngineering$3.9M7 / 1164%$1.2M
JacobsEngineering$18.2M7 / 1354%$6.8M
AECOMEngineering$20.1M6 / 1540%$7.4M
Burns & McDonnellEngineering$7.4M6 / 875%$2.2M

How the event numbers are defined

Same rules for every AI Pillar event so results compare

Registered / attendedDistinct people on the event campaign (Salesforce members, Cvent/Splash upload). Attended = member status Attended / In person / Virtual. De-duplicated by Contact/Lead and email; test and duplicate records removed first.
External vs Bentley@bentley.com registrants are reported as their own cohort and excluded from external show rate, accounts and ARR.
Accounts & ARR in the roomAttendees rolled up to the ultimate-parent account; ARR = Salesforce ARR field on that account (already annualized). Existing customer value, not event revenue.
Open pipeline in the roomOpportunities at those accounts with Created Date before the event date and not closed by it (close date on/after the event, or still open). Stage and amount are today’s values until opportunity history is in Snowflake.
Pipeline created afterOpportunities at attending accounts created within 90 days after the event (same window as the progression rule).
Where it fitsEach event is a child campaign under an AI Pillar sub-theme, so the overview row and this page fill in from the same campaign tree — no event-specific build.
How the Data Layer Comes Together Proposed end-to-end integration architecture

01 · Source Systems

Raw observations and transactional records

SalesforceCampaigns, members, contact/lead IDs, opportunities
Marketo + Event PlatformsEmail, forms, webinars, Cvent/Splash/ON24
GA4 + Search/LLMContent actions, sessions, referrals, web/AI visibility
PR, Social, PIKC, eStoreMentions, engagement, internal enablement, sales
→

02 · Snowflake PRESENTATION

Verified objects the model reads today

MART · SalesforceDIM_CAMPAIGN · FCT_CAMPAIGN_MEMBERSHIP · DIM_CONTACT / DIM_LEAD · FCT_OPPORTUNITY
EDW · Marketo + ON24MARKETO_ACTIVITIES_EMAIL_* · MARKETO_EMAILS / LEADS · EVENT / REGISTRANT / ATTENDEE
MART_MARKETING · GA4DAILY_GA4_PAGES_REPORT (AI Hub + AI blog paths, AI referrers)
EDW · Paid mediaMEDIA_GOOGLE / BING / LINKEDIN / FB_AD_ANALYTICS · MEDIA_LOOKUPS
→

03 · Power BI Semantic Model

Native SQL per table · campaign tree · guardrails in DAX

Campaign Tree + Membersrecursive walk from the four sub-themes · Responded · New vs Existing
Email · Webinar · Web Dailyrecipient × email · registrant × webinar · day × page × source
Paid Daily + Paid CampaignsSnowflake spend joined to the live list (DirectQuery) on campaign ID
Pipelineprimary campaign in tree or account touch before creation
→

04 · Executive / Diagnostic Views

Measures in Power BI, consistent across pages

Reach & Visibilityunique known people; SOV; LLM; web
Engagement & Progressionqualified actions; known contacts; MQLs
Commercial Impactdistinct intent; influenced pipeline; revenue
Dashboard & drilldownsexecutive scorecard + channel / source pages
Sources verified in Snowflake (PRESENTATION) on 10/20/26 and wired into the live Power BI model. The full SQL, campaign hierarchy, data flow diagram and DAX are on the Build Spec & SQL page. The illustrative charts on this wireframe still use sample data.
Data Model: Required Keys & Fact Grain
Trace any metric or chart Pick or search a KPI, chart or table to see what it answers, how it’s calculated, the joins, the DAX and a query you can run in Snowflake
Data Quality Gates Before Production
Campaign hierarchyEvery event/digital campaign maps to one AI Pillar child and parent Campaign ID. Avoid name-based matching when stable IDs exist.
Person resolution & dedupeMap Marketo lead, event registrant and Salesforce Contact/Lead to one governed person key; retain anonymous activity separately.
Response-status definitionNormalize registered vs attended, responded vs non-responded, known content engagement and qualified MQL transitions.
Timing & attributionProgression must follow first engagement; opportunity influence requires an earlier qualifying touch within a governed lookback window.
Different measure grainsPeople ≠ content actions ≠ pageviews ≠ media impressions ≠ opportunities ≠ orders. Never combine units in one sum.
Finance & cancellationsCompleted eStore sales use canonical order ID, completed status, net amount and the finance-approved revenue recognition date.
Sample Data Inspector See normalized records behind the illustrations
First 10 rows of the current filtered sample; data generated deterministically in the browser
Build Spec & SQL How the live Power BI model connects Snowflake, the campaign hierarchy and every system — for the developer or anyone picking this up later
Metric & chart tracer Every KPI and chart on every page → source system → Snowflake objects → joins → Power BI table → DAX → check query
One source of truthEverything is read from Snowflake PRESENTATION with native SQL, except the paid campaign list, which is read live from Nischal’s Power BI model.
Hierarchy drives scopeSalesforce campaign parent IDs decide what counts as AI Pillar. Add a child campaign under a sub-theme and it flows in on the next refresh.
Parameters, not editsCampaign IDs, email IDs, webinar IDs and the paid alignment value are parameters, so the same model works for the next campaign.
Units never mixedPeople, actions, pageviews, impressions and dollars stay in separate measures. vs prior period compares the same-length window before.

Data flow · source to report

What feeds each Power BI table, and where it lands in Snowflake first

1 · SOURCE SYSTEMSwhere the activity happens2 · SNOWFLAKE · PRESENTATIONreplicated nightly by the data team3 · POWER BI MODEL TABLESnative SQL, refreshed daily4 · MEASURES & REPORTwhat reviewers seeSnowflake: server vkelqpo-bird_prod.snowflakecomputing.com · DB PRESENTATION · WH REPORTING_WH__XSSalesforceCampaigns · members · opportunitiesMarketoAI campaign emails (mailing IDs)ON24Webinar registrants & attendeesGoogle Analytics 4AI Hub + blog pages, AI referrersAd platformsGoogle · LinkedIn · Microsoft · MetaPaid campaign listvia Nischal’s “Paid Media updated”Not connected yetPR/SOV · social · PIKC · ClickUpCampaign Treesub-theme → tactic hierarchyMembersone row per campaign memberPipelineinfluenced opportunitiesEmailrecipient × emailWebinarregistrant × webinarWeb Dailyday × page × sourcePaid Dailyday × platform campaignPaid Campaigns · DirectQuerylive list · filters Paid Daily to AIPlaceholder measuresshow “—” until a source existsMART.DIM_CAMPAIGNCampaign ID · parent ID · type · status · datesMART.FCT_CAMPAIGN_MEMBERSHIP+ member status · contact · lead dimsMART.FCT_OPPORTUNITY+ DIM_OPPORTUNITY · campaign & account keysEDW.MARKETO_ACTIVITIES_EMAIL_*send · delivered · open · click + emails, leadsEDW.EVENT · REGISTRANT · ATTENDEEON24 replica (current rows only)MART_MARKETING.DAILY_GA4_PAGES_REPORTdate · path · source · pageviews · sessionsEDW.MEDIA_*_AD_ANALYTICSGOOGLE · LINKEDIN · BING · FB + MEDIA_LOOKUPSKPI Measures (DAX) · Executive scorecardVisibilityTotal audience reached · Share of voice · LLM & web visibilityEngagementThought leadership engagement · Engaged known contactsProgressionAudience progression rate · MQLs from the campaignCommercial impactQualified intent signals · Influenced pipelineEvery KPIvs prior period = same-length window immediately beforePower BI report · daily refresh ~7 AM CTExecutive Scorecard9 KPI tiles · trend · funnel · web · AI referrals · teamTeam & Funnel Detailevery metric by team × funnel stage · emails · contentPublishedMy workspace · AI Pillar DashboardShared wireframecampaign-aipillar.pages.dev (comments)replicated / importedlive DirectQuery (no copy)not connected yet

Campaign hierarchy · how everything rolls up to the AI Pillar

Salesforce campaigns are the spine; every other system attaches to a child campaign

LEVEL 0pillarLEVEL 1sub-themesLEVEL 2+tactic campaignsPEOPLE &REVENUEInfrastructure AI Pillarroot campaign · 701Wj00001uusRZIAYAI for Asset Performance| Infrastructure AIConnected & Open AI| Infrastructure AIEmbedded AI| Infrastructure AIIndustry Narrative| Infrastructure AIEventsCvent / Splash →SFDC uploadWebinarsON24 → SFDCcampaignGated contentMarketo forms →SFDC campaignEmail programsMarketo program↔ SFDC campaignPaid mediaList row → SFDCChild Campaign IDUser groups / CABsattendees ascampaign membersToday: 0 childcampaigns underthe sub-themesCampaign members · FCT_CAMPAIGN_MEMBERSHIPContact / Lead ID · status · Responded · datesOpportunities · FCT_OPPORTUNITYprimary campaign in tree, or account touch before creationaccount keyfeeds ▸Reach · Engaged known contacts · Progression · MQLsfeeds ▸Influenced pipeline · influenced opportunitiesThe model walks the tree recursively from the four sub-theme IDs (up to 6 levels), so any campaign parented anywhere below a sub-theme is picked up automatically on the next refresh — no report change needed.

How each system attaches to the hierarchy

The join key that ties each source to a child campaign, and the KPIs it feeds

SystemHow it attachesKeyFeedsStatus
Salesforce events (Cvent / Splash upload)Child campaign under a sub-theme; attendees uploaded as members with statusCampaign Parent ID → sub-themeMembers, Reach, Engaged, New/Existing, MQLs, PipelineNeeds set-up
Marketo email programsMailing IDs listed in {{EmailIds}} today; target: program synced to an SFDC child campaignMailing ID (today) → SFDC Campaign ID (target)Email, Reach, Email CTO/CTR, AAG reachReady
Marketo gated content / formsForm campaign as SFDC child; Responded flag set on fillSFDC Campaign IDContent form fills, TLE, Engaged, ProgressionNeeds set-up
ON24 webinarsEvent IDs in {{WebinarEventIds}}; target: ON24 event linked to an SFDC child campaignEvent ID → SFDC Campaign IDWebinar registrations / attendance, TLEReady · none scheduled
GA4 webPage-path rules for AI Hub / AI blog; utm_campaign carries the child campaign key on campaign linksPath rules · utm_campaignLLM & web visibility, AI referrals, top content, TLEReady
Paid mediaRow in the paid campaign Microsoft List: Campaign Alignment = “Pillar - AI” + SFDC Child Campaign IDPlatform campaign ID → list → SFDC childPaid impressions / clicks / conversions (diagnostic)Ready
OpportunitiesPrimary campaign in the tree, or account member touch before creation; later Salesforce Campaign InfluencePrimary campaign key · ultimate account keyInfluenced pipeline & opportunitiesNeeds definition
PR / SOV, analyst briefings, social, PIKC, ClickUp (events, UG, CAB)No feed yet — placeholder measures show “—”—Share of voice, PR impressions, social, video, PIKC, activity countsManual entry

Connection & parameters

Power Query parameters used by every query (shown as {{Name}} in the SQL)

ParameterCurrent valuePurpose
SnowflakeServervkelqpo-bird_prod.snowflakecomputing.comSnowflake account
SnowflakeWarehouseREPORTING_WH__XSWarehouse used by the refresh
StartDate2024-07-01Earliest date loaded (gives prior-period history)
PillarRootId701Wj00001uusRZIAYAI Pillar root campaign (reference)
SubThemeIds'701Wj00001x4BSLIA2', '701Wj00001x5JE3IAM', '701Wj00001x4pG1IAI', '701Wj00001x5VofIAE'The four sub-themes the tree starts from
EmailIds31004,31138Marketo mailing IDs in scope
WebinarEventIds0ON24 event IDs in scope (none scheduled yet)
PaidAlignmentPillar - AICampaign Alignment value in the paid campaign list

Model, refresh & access

How the published model behaves

StorageImport for every Snowflake table; DirectQuery for Paid Campaigns (composite model)
RelationshipsAll facts → Date[Date] (single direction) · Members → Campaign Tree on campaign ID · Paid Daily → Paid Campaigns on platform campaign ID
Calculated tablesDate (1 Jul 2024 → today), Metric Catalog, Funnel Stage, Team Headline, TLE Type
Report filterCampaign window: Date ≥ 1 Oct 2025 (earlier data kept for prior-period comparisons)
RefreshDaily ~7 AM CT on the published dataset in My workspace
CredentialsSnowflake: OAuth2 (Maurice) · Paid Media updated: OAuth2 — both signed in on the dataset settings page
Native queriesDesktop asks to approve each new SQL text once; the service runs them without prompting
Project filesPower BI project (.pbip): report in PBIR folders, model in TMDL — diffable and versionable

Power BI tables · grain, source and logic

One line per model table; full SQL below

TableGrainSnowflake objectsLogicStatus
Campaign TreeOne row per campaign in the AI Pillar tree (sub-themes + every child below them)MART.DIM_CAMPAIGNRecursive CTE from the four sub-theme IDs down the Campaign Parent ID chain (max 6 levels). Tags each node with its sub-theme, level and Sub-theme / Tactic.Ready · 0 rows today
MembersOne row per campaign member on any campaign in the treeMART.FCT_CAMPAIGN_MEMBERSHIP · DIM_CAMPAIGN_MEMBER_STATUS · DIM_CONTACT · DIM_LEAD · FCT_LEADSame tree CTE, joined to membership. Responded = 1 when Has Campaign Member Responded is TRUE/YES/1. New vs Existing = person created between 1 day before and 14 days after the member record. Region from contact or lead.Ready · 0 rows today
PipelineOne row per opportunity influenced by the pillarMART.FCT_OPPORTUNITY · DIM_OPPORTUNITY · FCT_CAMPAIGN_MEMBERSHIP · DIM_CAMPAIGNInfluenced when (a) the opportunity’s primary campaign is in the tree, or (b) any member of the same ultimate account joined a tree campaign on or before the opportunity create date. Records which rule fired and the touch campaign.Ready · 0 rows today
EmailOne row per recipient × emailEDW.MARKETO_ACTIVITIES_EMAIL_SEND / DELIVERED / OPEN / CLICK · MARKETO_EMAILS · MARKETO_LEADSMailings filtered to {{EmailIds}}. Delivered / opened / clicked flags per recipient (bot clicks excluded). Segment = Internal enablement when the email address is @bentley.com, otherwise External market.Ready
WebinarOne row per registrant × webinarEDW.EVENT · REGISTRANT · ATTENDEE (ON24)Events filtered to {{WebinarEventIds}} (current rows only). Attended when the registrant email appears in ATTENDEE; minutes = live + archive.Ready · none scheduled
Web DailyOne row per day × page × source × campaignMART_MARKETING.DAILY_GA4_PAGES_REPORTAI pages = path contains infrastructure-ai, /category/ai/, or a /news/ or /blog/ path with -ai- / artificial-intelligence. Page group = AI Hub vs AI Blog & News. AI referral = session source ChatGPT/OpenAI, Gemini, Perplexity, Copilot or Claude.Ready
Paid DailyOne row per day × platform campaignEDW.MEDIA_GOOGLE / BING / LINKEDIN / FB_AD_ANALYTICS · MEDIA_LOOKUPSAll four platforms unioned from {{StartDate}}. The AI Pillar filter is applied in the measures through the live Paid Campaigns list (Campaign Alignment = “Pillar - AI”), joined on platform campaign ID.Ready
Paid CampaignsOne row per paid platform campaign (DirectQuery)Power BI semantic model “Paid Media updated” (Marketing Analytics Dashboards) · table Paid Media Campaign MetadataRead live with the viewer’s sign-in — no copy, no XMLA needed. Columns: CAMPAIGNID, Platform, Campaign Name, Campaign Alignment, SFDC Child Campaign ID, Funnel Stage, Region.Live (DirectQuery)
SQL behind each table Exactly what the model runs · parameters in pink · click to expand, copy to run in Snowflake (replace the parameters)
Campaign Tree · One row per campaign in the AI Pillar tree (sub-themes + every child below them) Ready · 0 rows today
Grain
One row per campaign in the AI Pillar tree (sub-themes + every child below them)
Reads
MART.DIM_CAMPAIGN
Logic
Recursive CTE from the four sub-theme IDs down the Campaign Parent ID chain (max 6 levels). Tags each node with its sub-theme, level and Sub-theme / Tactic.
with recursive tree (campaign_id, sub_theme_id, lvl) as ( select "Campaign ID", "Campaign ID", 1 from PRESENTATION.MART.DIM_CAMPAIGN where "Campaign ID" in ({{SubThemeIds}}) union all select c."Campaign ID", t.sub_theme_id, t.lvl + 1 from PRESENTATION.MART.DIM_CAMPAIGN c join tree t on c."Campaign Parent ID" = t.campaign_id where t.lvl < 6 ) select t.campaign_id, c."Campaign Name" as campaign_name, c."Campaign Parent ID" as parent_campaign_id, t.sub_theme_id, s."Campaign Name" as sub_theme, t.lvl as tree_level, iff(t.lvl = 1, 'Sub-theme', 'Tactic') as node_type, c."Campaign Type" as campaign_type, c."Campaign Status" as campaign_status, c."Campaign Start Date" as start_date, c."Campaign End Date" as end_date, '{{PillarRootId}}' as pillar_root_id from tree t join PRESENTATION.MART.DIM_CAMPAIGN c on c."Campaign ID" = t.campaign_id join PRESENTATION.MART.DIM_CAMPAIGN s on s."Campaign ID" = t.sub_theme_id
Members · One row per campaign member on any campaign in the tree Ready · 0 rows today
Grain
One row per campaign member on any campaign in the tree
Reads
MART.FCT_CAMPAIGN_MEMBERSHIP · DIM_CAMPAIGN_MEMBER_STATUS · DIM_CONTACT · DIM_LEAD · FCT_LEAD
Logic
Same tree CTE, joined to membership. Responded = 1 when Has Campaign Member Responded is TRUE/YES/1. New vs Existing = person created between 1 day before and 14 days after the member record. Region from contact or lead.
with recursive tree (campaign_id, sub_theme_id, lvl) as ( select "Campaign ID", "Campaign ID", 1 from PRESENTATION.MART.DIM_CAMPAIGN where "Campaign ID" in ({{SubThemeIds}}) union all select c."Campaign ID", t.sub_theme_id, t.lvl + 1 from PRESENTATION.MART.DIM_CAMPAIGN c join tree t on c."Campaign Parent ID" = t.campaign_id where t.lvl < 6 ) select m.PK_CAMPAIGN_MEMBERSHIP_KEY as membership_key, m."Campaign ID" as campaign_id, t.sub_theme_id, m.FK_CONTACT_OR_LEAD_KEY as person_key, iff(m.FK_CONTACT_KEY is not null, 'Contact', 'Lead') as person_type, m.FK_ULTIMATE_KEY as account_key, st."Campaign Member Status" as member_status, iff(upper(m."Has Campaign Member Responded") in ('TRUE','YES','1'), 1, 0) as responded, m."Campaign Member Created At"::date as member_created_date, m."Campaign Member First Responded At"::date as first_responded_date, coalesce(to_date(to_char(ct.FK_CONTACT_CREATED_DATE_KEY), 'YYYYMMDD'), to_date(to_char(fl.FK_LEAD_CREATED_DATE_KEY), 'YYYYMMDD')) as person_created_date, iff(datediff('day', coalesce(to_date(to_char(ct.FK_CONTACT_CREATED_DATE_KEY), 'YYYYMMDD'), to_date(to_char(fl.FK_LEAD_CREATED_DATE_KEY), 'YYYYMMDD')), m."Campaign Member Created At"::date) between -1 and 14, 'New', 'Existing') as new_vs_existing, coalesce(ct."Contact Organizational Region", ld."Lead Organizational Region") as region from PRESENTATION.MART.FCT_CAMPAIGN_MEMBERSHIP m join tree t on t.campaign_id = m."Campaign ID" left join PRESENTATION.MART.DIM_CAMPAIGN_MEMBER_STATUS st on st.SK_CAMPAIGN_MEMBER_STATUS_KEY = m.FK_CAMPAIGN_MEMBER_STATUS_KEY left join PRESENTATION.MART.DIM_CONTACT ct on ct.SK_CONTACT_KEY = m.FK_CONTACT_KEY left join PRESENTATION.MART.DIM_LEAD ld on ld.SK_LEAD_KEY = m.FK_LEAD_KEY left join PRESENTATION.MART.FCT_LEAD fl on fl.FK_LEAD_KEY = m.FK_LEAD_KEY
Pipeline · One row per opportunity influenced by the pillar Ready · 0 rows today
Grain
One row per opportunity influenced by the pillar
Reads
MART.FCT_OPPORTUNITY · DIM_OPPORTUNITY · FCT_CAMPAIGN_MEMBERSHIP · DIM_CAMPAIGN
Logic
Influenced when (a) the opportunity’s primary campaign is in the tree, or (b) any member of the same ultimate account joined a tree campaign on or before the opportunity create date. Records which rule fired and the touch campaign.
with recursive tree (campaign_id, sub_theme_id, lvl) as ( select "Campaign ID", "Campaign ID", 1 from PRESENTATION.MART.DIM_CAMPAIGN where "Campaign ID" in ({{SubThemeIds}}) union all select c."Campaign ID", t.sub_theme_id, t.lvl + 1 from PRESENTATION.MART.DIM_CAMPAIGN c join tree t on c."Campaign Parent ID" = t.campaign_id where t.lvl < 6 ), tree_keys as ( select dc.SK_CAMPAIGN_KEY, t.campaign_id, t.sub_theme_id from tree t join PRESENTATION.MART.DIM_CAMPAIGN dc on dc."Campaign ID" = t.campaign_id ), acct_touch as ( select m.FK_ULTIMATE_KEY, min(m."Campaign Member Created At"::date) as first_touch_date, min_by(t.campaign_id, m."Campaign Member Created At") as first_touch_campaign from PRESENTATION.MART.FCT_CAMPAIGN_MEMBERSHIP m join tree t on t.campaign_id = m."Campaign ID" where m.FK_ULTIMATE_KEY is not null group by 1 ) select o.PK_OPPORTUNITY_KEY as opportunity_key, d."Opportunity ID" as opportunity_id, d."Opportunity Name" as opportunity_name, d."Opportunity Stage" as stage, d."Opportunity Status" as status, o."Opportunity Create Date" as created_date, o."Opportunity Close Date" as close_date, o."Opportunity Amount USD" as amount_usd, o.FK_ULTIMATE_KEY as account_key, iff(pk.campaign_id is not null, 'Primary campaign', 'Account touch before creation') as touch_rule, coalesce(pk.campaign_id, at.first_touch_campaign) as touch_campaign_id, at.first_touch_date from PRESENTATION.MART.FCT_OPPORTUNITY o join PRESENTATION.MART.DIM_OPPORTUNITY d on d.SK_OPPORTUNITY_KEY = o.FK_OPPORTUNITY_KEY left join tree_keys pk on pk.SK_CAMPAIGN_KEY = o.FK_PRIMARY_CAMPAIGN_KEY left join acct_touch at on at.FK_ULTIMATE_KEY = o.FK_ULTIMATE_KEY and at.first_touch_date <= o."Opportunity Create Date" where o."Opportunity Create Date" >= '{{StartDate}}' and (pk.campaign_id is not null or at.FK_ULTIMATE_KEY is not null)
Email · One row per recipient × email Ready
Grain
One row per recipient × email
Reads
EDW.MARKETO_ACTIVITIES_EMAIL_SEND / DELIVERED / OPEN / CLICK · MARKETO_EMAILS · MARKETO_LEADS
Logic
Mailings filtered to {{EmailIds}}. Delivered / opened / clicked flags per recipient (bot clicks excluded). Segment = Internal enablement when the email address is @bentley.com, otherwise External market.
with s as ( select LEADID, MAILINGID, min(ACTIVITY_DATE)::date as sent_date from PRESENTATION.EDW.MARKETO_ACTIVITIES_EMAIL_SEND where MAILINGID in ({{EmailIds}}) group by 1,2 ), d as ( select distinct LEADID, MAILINGID from PRESENTATION.EDW.MARKETO_ACTIVITIES_EMAIL_DELIVERED where MAILINGID in ({{EmailIds}}) ), o as ( select distinct LEADID, MAILINGID from PRESENTATION.EDW.MARKETO_ACTIVITIES_EMAIL_OPEN where MAILINGID in ({{EmailIds}}) ), c as ( select distinct LEADID, MAILINGID from PRESENTATION.EDW.MARKETO_ACTIVITIES_EMAIL_CLICK where MAILINGID in ({{EmailIds}}) and not coalesce(IS_BOT_ACTIVITY, false) ) select s.LEADID as marketo_lead_id, lower(ml.EMAIL) as email, s.MAILINGID as email_id, e.NAME as email_name, s.sent_date, iff(d.LEADID is null, 0, 1) as delivered, iff(o.LEADID is null, 0, 1) as opened, iff(c.LEADID is null, 0, 1) as clicked, iff(lower(ml.EMAIL) like '%@bentley.com', 'Internal enablement', 'External market') as segment from s left join d on d.LEADID = s.LEADID and d.MAILINGID = s.MAILINGID left join o on o.LEADID = s.LEADID and o.MAILINGID = s.MAILINGID left join c on c.LEADID = s.LEADID and c.MAILINGID = s.MAILINGID left join PRESENTATION.EDW.MARKETO_EMAILS e on e.ID = s.MAILINGID left join PRESENTATION.EDW.MARKETO_LEADS ml on ml.ID = s.LEADID
Webinar · One row per registrant × webinar Ready · none scheduled
Grain
One row per registrant × webinar
Reads
EDW.EVENT · REGISTRANT · ATTENDEE (ON24)
Logic
Events filtered to {{WebinarEventIds}} (current rows only). Attended when the registrant email appears in ATTENDEE; minutes = live + archive.
select e.EVENT_ID as event_id, e.EVENT_NAME as event_name, e.EVENT_LIVE_START::date as live_date, lower(r.EMAIL) as email, r.CREATE_TIMESTAMP::date as registered_date, r.UTM_CAMPAIGN as utm_campaign, iff(a.EMAIL is null, 0, 1) as attended, a.ENGAGEMENT_SCORE as engagement_score, coalesce(a.LIVE_MINUTE, 0) + coalesce(a.ARCHIVE_MINUTE, 0) as minutes_viewed from PRESENTATION.EDW.EVENT e join PRESENTATION.EDW.REGISTRANT r on r.EVENT_ID = e.EVENT_ID and r.IS_CURRENT_ROW left join PRESENTATION.EDW.ATTENDEE a on a.EVENT_ID = e.EVENT_ID and lower(a.EMAIL) = lower(r.EMAIL) and a.IS_CURRENT_ROW where e.IS_CURRENT_ROW and e.EVENT_ID in ({{WebinarEventIds}})
Web Daily · One row per day × page × source × campaign Ready
Grain
One row per day × page × source × campaign
Reads
MART_MARKETING.DAILY_GA4_PAGES_REPORT
Logic
AI pages = path contains infrastructure-ai, /category/ai/, or a /news/ or /blog/ path with -ai- / artificial-intelligence. Page group = AI Hub vs AI Blog & News. AI referral = session source ChatGPT/OpenAI, Gemini, Perplexity, Copilot or Claude.
select "Event Date" as event_date, case when ("Path" ilike '%/news/%' or "Path" ilike '%/blog/%') then 'AI Blog & News' when "Path" ilike '%infrastructure-ai%' then 'AI Hub' else 'AI Blog & News' end as page_group, "Path" as path, "Country" as country, "Session Default Channel Group" as channel_group, "Session Source" as session_source, "Session Campaign Name" as utm_campaign, iff("Session Source" ilike any ('%chatgpt%','%openai%','%perplexity%','%copilot%','%gemini%','claude%'), 1, 0) as is_ai_referral, case when "Session Source" ilike any ('%chatgpt%','%openai%') then 'ChatGPT' when "Session Source" ilike '%gemini%' then 'Gemini' when "Session Source" ilike '%perplexity%' then 'Perplexity' when "Session Source" ilike '%copilot%' then 'Copilot' when "Session Source" ilike 'claude%' then 'Claude' end as ai_assistant, sum("Pageviews") as pageviews, sum("Users") as users, sum("Sessions") as sessions, sum("Engaged Sessions") as engaged_sessions from PRESENTATION.MART_MARKETING.DAILY_GA4_PAGES_REPORT where "Event Date" >= '{{StartDate}}' and ( "Path" ilike '%infrastructure-ai%' or "Path" ilike '%/category/ai/%' or (("Path" ilike '%/news/%' or "Path" ilike '%/blog/%') and ("Path" ilike '%-ai-%' or "Path" ilike '%-ai/%' or "Path" ilike '%artificial-intelligence%')) ) group by 1,2,3,4,5,6,7,8,9
Paid Daily · One row per day × platform campaign Ready
Grain
One row per day × platform campaign
Reads
EDW.MEDIA_GOOGLE / BING / LINKEDIN / FB_AD_ANALYTICS · MEDIA_LOOKUPS
Logic
All four platforms unioned from {{StartDate}}. The AI Pillar filter is applied in the measures through the live Paid Campaigns list (Campaign Alignment = “Pillar - AI”), joined on platform campaign ID.
with paid as ( select 'Google' as platform, REPORTED_DATE, CAMPAIGNID, IMPRESSIONS, CLICKS, COST, CONVERSIONS, null::number as leads, VIDEO_VIEWS from PRESENTATION.EDW.MEDIA_GOOGLE_AD_ANALYTICS union all select 'Microsoft Advertising', REPORTED_DATE, CAMPAIGNID, IMPRESSIONS, CLICKS, COST, CONVERSIONS, null, null from PRESENTATION.EDW.MEDIA_BING_AD_ANALYTICS union all select 'LinkedIn', REPORTED_DATE, CAMPAIGNID, IMPRESSIONS, CLICKS, COST, CONVERSIONS, LEADS, VIDEO_VIEWS from PRESENTATION.EDW.MEDIA_LINKEDIN_AD_ANALYTICS union all select 'Meta', REPORTED_DATE, CAMPAIGNID, IMPRESSIONS, CLICKS, COST, null, LEADS, VIDEO_VIEWS from PRESENTATION.EDW.MEDIA_FB_AD_ANALYTICS ) select p.platform, p.REPORTED_DATE as report_date, p.CAMPAIGNID as platform_campaign_id, max(l.CAMPAIGN_NAME) as platform_campaign_name, sum(p.IMPRESSIONS) as impressions, sum(p.CLICKS) as clicks, sum(p.COST) as cost, sum(p.CONVERSIONS) as conversions, sum(p.leads) as leads, sum(p.VIDEO_VIEWS) as video_views from paid p left join (select distinct CAMPAIGNID, CAMPAIGN_NAME from PRESENTATION.EDW.MEDIA_LOOKUPS) l on l.CAMPAIGNID = p.CAMPAIGNID where p.REPORTED_DATE >= '{{StartDate}}' group by 1,2,3
Paid Campaigns · One row per paid platform campaign (DirectQuery) Live (DirectQuery)
Source
Power BI semantic model Paid Media updated in Marketing Analytics Dashboards (owner: Nischal)
Mode
DirectQuery (composite model) — the AI Pillar model holds no copy
Upstream list
SharePoint Microsoft List Paid Media Campaign Metadata (site 2020MarketingPlans) — open list. Media owners keep Campaign Alignment and SFDC Child Campaign ID filled in there; that ID is what places a paid campaign in the AI Pillar campaign tree.
Why not SQL
The list is maintained in a Microsoft List behind Nischal’s model; that workspace is not on Premium capacity, so an import refresh through XMLA fails in the service. DirectQuery works on Pro.
let Source = AnalysisServices.Database( "powerbi://api.powerbi.com/v1.0/myorg/Marketing%20Analytics%20Dashboards", "Paid Media updated"), Cubes = Table.Combine(Source[Data]), Cube = Cubes{[Id="Model", Kind="Cube"]}[Data] in Cube // table: Paid Media Campaign Metadata → local table Paid Campaigns
Event impact · open pipeline in the room · one row per turnout group × stage, for one event Needs set-up
Grain
Event × account turnout × opportunity stage
Reads
MART.DIM_CAMPAIGN · FCT_CAMPAIGN_MEMBERSHIP · DIM_CAMPAIGN_MEMBER_STATUS · FCT_OPPORTUNITY · DIM_OPPORTUNITY
Logic
Accounts = ultimate parents of the event’s members (attended if any member attended). Open in the room = created before the event date and not closed by it. Stage/amount are current values until opportunity history lands in Snowflake.
-- Open pipeline in the room for one event campaign -- (replace {{EventCampaignId}}; in the model this runs for every event campaign in the AI Pillar tree) with ev as ( select "Campaign ID" as campaign_id, "Campaign Start Date"::date as event_date from PRESENTATION.MART.DIM_CAMPAIGN where "Campaign ID" = '{{EventCampaignId}}' ), accts as ( select m.FK_ULTIMATE_KEY as account_key, max(iff(st."Campaign Member Status" ilike 'attended%', 1, 0)) as attended from PRESENTATION.MART.FCT_CAMPAIGN_MEMBERSHIP m join ev on ev.campaign_id = m."Campaign ID" left join PRESENTATION.MART.DIM_CAMPAIGN_MEMBER_STATUS st on st.SK_CAMPAIGN_MEMBER_STATUS_KEY = m.FK_CAMPAIGN_MEMBER_STATUS_KEY where m.FK_ULTIMATE_KEY is not null group by 1 ) select iff(a.attended = 1, 'Attended', 'Registered, no one came') as account_turnout, d."Opportunity Stage" as stage, count(distinct o.PK_OPPORTUNITY_KEY) as open_opps, sum(o."Opportunity Amount USD") as open_pipeline_usd from PRESENTATION.MART.FCT_OPPORTUNITY o join PRESENTATION.MART.DIM_OPPORTUNITY d on d.SK_OPPORTUNITY_KEY = o.FK_OPPORTUNITY_KEY join accts a on a.account_key = o.FK_ULTIMATE_KEY cross join ev where o."Opportunity Create Date" < ev.event_date and (o."Opportunity Close Date" >= ev.event_date or d."Opportunity Status" = 'Open') group by 1, 2 order by 1, 4 desc; -- Pipeline created within 90 days after the event, at attending accounts -- ... same ev / accts CTEs ... -- where a.attended = 1 -- and o."Opportunity Create Date" between ev.event_date and dateadd('day', 90, ev.event_date)
Measure logic (DAX) How the headline numbers are calculated in the model

Total audience reached (external)

[Email Recipients (External)] + [Event & Community People] -- Email Recipients (External) CALCULATE(DISTINCTCOUNT('Email'[EMAIL]), 'Email'[DELIVERED] = 1, 'Email'[SEGMENT] = "External market") -- Event & Community People COALESCE(DISTINCTCOUNT('Members'[PERSON_KEY]), 0)

LLM & web visibility

[AI Hub Pageviews] + [AI Blog Pageviews] -- AI Hub Pageviews CALCULATE(SUM('Web Daily'[PAGEVIEWS]), 'Web Daily'[PAGE_GROUP] = "AI Hub") -- AI Referral Sessions (shown separately, never summed with pageviews) CALCULATE(SUM('Web Daily'[SESSIONS]), 'Web Daily'[IS_AI_REFERRAL] = 1)

Thought leadership engagement

[Webinar Registrations] + CALCULATE(COUNTROWS('Members'), 'Members'[RESPONDED] = 1) + CALCULATE(SUM('Web Daily'[ENGAGED_SESSIONS]), 'Web Daily'[PAGE_GROUP] = "AI Hub") -- video views, PIKC downloads, social and form fills join as their sources land

Engaged known contacts

COALESCE(CALCULATE(DISTINCTCOUNT('Members'[PERSON_KEY]), 'Members'[RESPONDED] = 1), 0)

Influenced pipeline

COALESCE(SUM('Pipeline'[AMOUNT_USD]), 0)

Paid media (diagnostic)

CALCULATE(SUM('Paid Daily'[IMPRESSIONS]), KEEPFILTERS('Paid Campaigns'[CAMPAIGN_ALIGNMENT] = "Pillar - AI"))

vs prior period (every KPI)

VAR _min = MIN('Date'[Date]) VAR _max = MAX('Date'[Date]) VAR _d = INT(_max - _min) + 1 RETURN CALCULATE([<KPI>], DATESBETWEEN('Date'[Date], _min - _d, _min - 1)) -- label: ▲/▼ % change (points for rates); green up, red down; "no prior period" when blank
Open items before the numbers are final
Child campaigns under the sub-themesThe four sub-themes have no children and no members in Snowflake today, so events, engaged contacts, progression, MQLs and pipeline read 0. Parent every AI event, webinar, form and paid child campaign to one sub-theme in Salesforce.
De-duplicate people across channelsAudience = email recipients + campaign members. Email is keyed on lower-case email, members on Contact/Lead key, so one person in both is counted twice today. Fix: resolve Marketo lead → SFDC Contact/Lead (MARKETO_LEADS SFDC ID, else email) and count distinct person keys; drop test records.
Responded flag on event statusesEngaged known contacts relies on Has Campaign Member Responded. Set it for Attended / Visited booth / Meeting held statuses on event campaigns.
MQL date in SnowflakeMQLs from the campaign need the MQL timestamp on lead/contact; not in MART today.
Intent requestsDemo / trial / contact-sales / EA-LA need the product CTA form list mapped to request type (trial and EA/LA source unknown).
Credentials for refreshSnowflake (OAuth2) and Paid Media updated (OAuth2) must be signed in on the published dataset before the daily refresh and the Team & Funnel page work in the service.
Reuse this for the next campaign
Build the campaign tree in SalesforceCreate the root and sub-theme campaigns; parent every tactic campaign (events, webinars, forms, email, paid) to a sub-theme.
Set member statuses + RespondedUse the standard status set and tick Responded for the qualifying statuses so engagement and progression work.
Tag the other systemsMarketo programs synced to SFDC children; ON24 events linked; paid list rows with the alignment value + SFDC Child Campaign ID; utm_campaign = child campaign key.
Change the parametersCopy the .pbip, then set SubThemeIds, PillarRootId, EmailIds, WebinarEventIds, PaidAlignment and StartDate. No SQL edits needed.
Refresh + sign inRefresh in Desktop (approve the native queries once), publish, sign in Snowflake + Paid Media credentials, schedule the daily refresh.
ValidateCheck tree row count, member and email counts against Salesforce / Marketo, and de-dup people before sharing numbers.
Executive KPI Definitions
Individual Metrics → Executive Outcomes Directly mapped from the workbook’s KPI Mapping Detail tab
“Proposed / needs validation” means a business rule or integration has not been verified. The demo numbers reflect illustrative model behavior only; they do not imply data readiness.
Feedback summary Every comment left on this wireframe, by page
Reference design: Bentley Sale Week dashboard screenshot · KPI source: AI Pillar Campaign Metrics workbook (Sheet1, Executive Scorecard, KPI Mapping Detail) · All displayed numbers, products, opportunities, and accounts are synthetic. Filters affect simulated data and metric measures; nothing is queried from production systems.