[{"data":1,"prerenderedAt":7190},["ShallowReactive",2],{"tutorial-marketing-analyst-101/ingest-data":3,"content-query-8SqA42R0kY":1109,"content-query-7arUCiBf4n":1963},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"readingTime":11,"category":12,"tags":13,"difficulty":18,"module":5,"step":19,"journeys":20,"roles":22,"learnMore":25,"author":35,"body":39,"_type":1103,"_id":1104,"_source":1105,"_file":1106,"_stem":1107,"_extension":1108},"/tutorials/marketing-analyst-101/ingest-data","marketing-analyst-101",false,"","Ingest Google Ads, Klaviyo, and GA4","Have the AI agent write ingestr assets for three marketing data sources and pull ad spend, email engagement, and web analytics into DuckDB.","2026-04-24",14,"Tutorial",[14,15,16,17],"Bruin CLI","Bruin AI","DuckDB","E-commerce","Beginner",3,[21],"Marketing Analyst",[23,24],"Marketing","Data Analyst",[26,29,32],{"label":27,"url":28},"ingestr supported sources","https://bruin-data.github.io/ingestr/supported-sources.html",{"label":30,"url":31},"Bruin ingestr asset type","https://getbruin.com/docs/bruin/assets/ingestr.html",{"label":33,"url":34},"Shopify Data Pipeline (ingest-data reference)","/learn/ecommerce-pipeline/ingest-data",{"name":36,"role":37,"image":38},"Bruin Team","Bruin Data","/bruin-logo-2025.svg",{"type":40,"children":41,"toc":1092},"root",[42,51,57,63,68,104,109,115,289,295,300,417,474,486,523,529,534,681,686,876,895,901,906,922,942,948,953,999,1004,1010,1068,1074,1086],{"type":43,"tag":44,"props":45,"children":47},"element","h2",{"id":46},"what-youll-do",[48],{"type":49,"value":50},"text","What you'll do",{"type":43,"tag":52,"props":53,"children":54},"p",{},[55],{"type":49,"value":56},"Get credentials for Google Ads, Klaviyo, and GA4, add them to your project config, and have the AI agent scaffold three ingestr assets. Then run the pipeline once to populate DuckDB.",{"type":43,"tag":44,"props":58,"children":60},{"id":59},"why-this-step-matters",[61],{"type":49,"value":62},"Why this step matters",{"type":43,"tag":52,"props":64,"children":65},{},[66],{"type":49,"value":67},"This is the entire data-import layer of a marketing analyst's stack, in one step:",{"type":43,"tag":69,"props":70,"children":71},"ul",{},[72,84,94],{"type":43,"tag":73,"props":74,"children":75},"li",{},[76,82],{"type":43,"tag":77,"props":78,"children":79},"strong",{},[80],{"type":49,"value":81},"Google Ads",{"type":49,"value":83}," tells you what you paid to acquire traffic - campaigns, clicks, cost, conversions",{"type":43,"tag":73,"props":85,"children":86},{},[87,92],{"type":43,"tag":77,"props":88,"children":89},{},[90],{"type":49,"value":91},"Klaviyo",{"type":49,"value":93}," tells you what happens after you have someone's email - engagement, flow performance, revenue attribution",{"type":43,"tag":73,"props":95,"children":96},{},[97,102],{"type":43,"tag":77,"props":98,"children":99},{},[100],{"type":49,"value":101},"GA4",{"type":49,"value":103}," tells you what they did on your site - sessions, source/medium, conversion events",{"type":43,"tag":52,"props":105,"children":106},{},[107],{"type":49,"value":108},"Most marketing dashboards show each of these in isolation. With all three in the same database you can finally ask cross-channel questions - which is where the real insight lives (and what your vendor BI tool usually won't let you do without a custom connector).",{"type":43,"tag":44,"props":110,"children":112},{"id":111},"_1-gather-credentials-for-each-source",[113],{"type":49,"value":114},"1. Gather credentials for each source",{"type":43,"tag":116,"props":117,"children":119},"variant-tabs",{":variants":118},"[{\"id\":\"google-ads\",\"label\":\"Google Ads\"},{\"id\":\"klaviyo\",\"label\":\"Klaviyo\"},{\"id\":\"ga4\",\"label\":\"GA4\"}]",[120,210,241],{"type":43,"tag":121,"props":122,"children":123},"template",{"v-slot:google-ads":7},[124,130,135,188],{"type":43,"tag":125,"props":126,"children":128},"h3",{"id":127},"google-ads",[129],{"type":49,"value":81},{"type":43,"tag":52,"props":131,"children":132},{},[133],{"type":49,"value":134},"You need three things:",{"type":43,"tag":69,"props":136,"children":137},{},[138,148,158],{"type":43,"tag":73,"props":139,"children":140},{},[141,146],{"type":43,"tag":77,"props":142,"children":143},{},[144],{"type":49,"value":145},"Developer token",{"type":49,"value":147}," - from your Google Ads Manager account: Tools → API Center. Apply for one if you don't have it (usually approved same day for \"test account\" tier).",{"type":43,"tag":73,"props":149,"children":150},{},[151,156],{"type":43,"tag":77,"props":152,"children":153},{},[154],{"type":49,"value":155},"Customer ID",{"type":49,"value":157}," - the 10-digit number shown in the top-right of the Google Ads UI (remove the dashes).",{"type":43,"tag":73,"props":159,"children":160},{},[161,166,168,177,179,186],{"type":43,"tag":77,"props":162,"children":163},{},[164],{"type":49,"value":165},"OAuth credentials",{"type":49,"value":167}," - create a client ID + secret in ",{"type":43,"tag":169,"props":170,"children":174},"a",{"href":171,"rel":172},"https://console.cloud.google.com/apis/credentials",[173],"nofollow",[175],{"type":49,"value":176},"Google Cloud Console",{"type":49,"value":178},", then follow the ",{"type":43,"tag":169,"props":180,"children":183},{"href":181,"rel":182},"https://bruin-data.github.io/ingestr/supported-sources/google-ads.html",[173],[184],{"type":49,"value":185},"ingestr Google Ads guide",{"type":49,"value":187}," to get a refresh token.",{"type":43,"tag":189,"props":190,"children":191},"blockquote",{},[192],{"type":43,"tag":52,"props":193,"children":194},{},[195,200,202,208],{"type":43,"tag":77,"props":196,"children":197},{},[198],{"type":49,"value":199},"Shortcut:",{"type":49,"value":201}," if this is your first API integration, ask your AI agent: ",{"type":43,"tag":203,"props":204,"children":205},"em",{},[206],{"type":49,"value":207},"\"Walk me through getting a Google Ads refresh token for ingestr, step by step.\"",{"type":49,"value":209}," It knows the whole flow.",{"type":43,"tag":121,"props":211,"children":212},{"v-slot:klaviyo":7},[213,218,223,236],{"type":43,"tag":125,"props":214,"children":216},{"id":215},"klaviyo",[217],{"type":49,"value":91},{"type":43,"tag":52,"props":219,"children":220},{},[221],{"type":49,"value":222},"One thing:",{"type":43,"tag":69,"props":224,"children":225},{},[226],{"type":43,"tag":73,"props":227,"children":228},{},[229,234],{"type":43,"tag":77,"props":230,"children":231},{},[232],{"type":49,"value":233},"Private API key",{"type":49,"value":235}," - in Klaviyo: Account → Settings → API Keys → Create Private API Key. Grant read-only scopes for Lists, Campaigns, Flows, Metrics, Profiles.",{"type":43,"tag":52,"props":237,"children":238},{},[239],{"type":49,"value":240},"Copy the key immediately - Klaviyo only shows it once.",{"type":43,"tag":121,"props":242,"children":243},{"v-slot:ga4":7},[244,249,254],{"type":43,"tag":125,"props":245,"children":247},{"id":246},"ga4",[248],{"type":49,"value":101},{"type":43,"tag":52,"props":250,"children":251},{},[252],{"type":49,"value":253},"Three things:",{"type":43,"tag":69,"props":255,"children":256},{},[257,267,277],{"type":43,"tag":73,"props":258,"children":259},{},[260,265],{"type":43,"tag":77,"props":261,"children":262},{},[263],{"type":49,"value":264},"Property ID",{"type":49,"value":266}," - the numeric GA4 property ID (in GA4: Admin → Property Settings → Property ID).",{"type":43,"tag":73,"props":268,"children":269},{},[270,275],{"type":43,"tag":77,"props":271,"children":272},{},[273],{"type":49,"value":274},"Service account JSON",{"type":49,"value":276}," - create a service account in Google Cloud Console with the \"Google Analytics Data API\" enabled. Download the key file.",{"type":43,"tag":73,"props":278,"children":279},{},[280,282,287],{"type":49,"value":281},"Grant the service account ",{"type":43,"tag":77,"props":283,"children":284},{},[285],{"type":49,"value":286},"Viewer",{"type":49,"value":288}," access inside GA4 (Admin → Property Access Management → add the service account's email as Viewer).",{"type":43,"tag":44,"props":290,"children":292},{"id":291},"_2-add-the-source-connections",[293],{"type":49,"value":294},"2. Add the source connections",{"type":43,"tag":52,"props":296,"children":297},{},[298],{"type":49,"value":299},"Prompt the agent:",{"type":43,"tag":301,"props":302,"children":303},"prompt-block",{},[304,326,388,408],{"type":43,"tag":52,"props":305,"children":306},{},[307,309,316,318,324],{"type":49,"value":308},"Add three new connections to ",{"type":43,"tag":310,"props":311,"children":313},"code",{"className":312},[],[314],{"type":49,"value":315},".bruin.yml",{"type":49,"value":317}," under ",{"type":43,"tag":310,"props":319,"children":321},{"className":320},[],[322],{"type":49,"value":323},"environments.default.connections",{"type":49,"value":325},":",{"type":43,"tag":327,"props":328,"children":329},"ol",{},[330,351,369],{"type":43,"tag":73,"props":331,"children":332},{},[333,335,341,343,349],{"type":49,"value":334},"A ",{"type":43,"tag":310,"props":336,"children":338},{"className":337},[],[339],{"type":49,"value":340},"google_ads",{"type":49,"value":342}," connection named ",{"type":43,"tag":310,"props":344,"children":346},{"className":345},[],[347],{"type":49,"value":348},"google-ads-default",{"type":49,"value":350}," with the developer token, customer ID, client ID, client secret, and refresh token I'll paste below",{"type":43,"tag":73,"props":352,"children":353},{},[354,355,360,361,367],{"type":49,"value":334},{"type":43,"tag":310,"props":356,"children":358},{"className":357},[],[359],{"type":49,"value":215},{"type":49,"value":342},{"type":43,"tag":310,"props":362,"children":364},{"className":363},[],[365],{"type":49,"value":366},"klaviyo-default",{"type":49,"value":368}," with my Klaviyo private API key",{"type":43,"tag":73,"props":370,"children":371},{},[372,373,379,380,386],{"type":49,"value":334},{"type":43,"tag":310,"props":374,"children":376},{"className":375},[],[377],{"type":49,"value":378},"google_analytics",{"type":49,"value":342},{"type":43,"tag":310,"props":381,"children":383},{"className":382},[],[384],{"type":49,"value":385},"ga4-default",{"type":49,"value":387}," with my GA4 property ID and path to the service account JSON file",{"type":43,"tag":52,"props":389,"children":390},{},[391,393,398,400,406],{"type":49,"value":392},"Use the ingestr docs at ",{"type":43,"tag":169,"props":394,"children":396},{"href":28,"rel":395},[173],[397],{"type":49,"value":28},{"type":49,"value":399}," for the exact field names per source. Then run ",{"type":43,"tag":310,"props":401,"children":403},{"className":402},[],[404],{"type":49,"value":405},"bruin connections test",{"type":49,"value":407}," for each and show me the output.",{"type":43,"tag":52,"props":409,"children":410},{},[411],{"type":43,"tag":412,"props":413,"children":414},"span",{},[415],{"type":49,"value":416},"paste your credentials here - or point the agent at where they're stored",{"type":43,"tag":189,"props":418,"children":419},{},[420],{"type":43,"tag":52,"props":421,"children":422},{},[423,428,430,435,437,442,444,450,452,458,459,465,467,472],{"type":43,"tag":77,"props":424,"children":425},{},[426],{"type":49,"value":427},"Don't want to hand the agent your keys?",{"type":49,"value":429}," Ask it instead to scaffold the connections in ",{"type":43,"tag":310,"props":431,"children":433},{"className":432},[],[434],{"type":49,"value":315},{"type":49,"value":436}," with ",{"type":43,"tag":77,"props":438,"children":439},{},[440],{"type":49,"value":441},"placeholder values",{"type":49,"value":443}," (e.g. ",{"type":43,"tag":310,"props":445,"children":447},{"className":446},[],[448],{"type":49,"value":449},"\u003CYOUR_GOOGLE_ADS_DEV_TOKEN>",{"type":49,"value":451},", ",{"type":43,"tag":310,"props":453,"children":455},{"className":454},[],[456],{"type":49,"value":457},"\u003CYOUR_KLAVIYO_KEY>",{"type":49,"value":451},{"type":43,"tag":310,"props":460,"children":462},{"className":461},[],[463],{"type":49,"value":464},"\u003CYOUR_GA4_PROPERTY_ID>",{"type":49,"value":466},"). Then open ",{"type":43,"tag":310,"props":468,"children":470},{"className":469},[],[471],{"type":49,"value":315},{"type":49,"value":473}," yourself and paste the real keys in. The file is git-ignored by default, so they stay local.",{"type":43,"tag":52,"props":475,"children":476},{},[477,479,484],{"type":49,"value":478},"When the agent finishes, ",{"type":43,"tag":310,"props":480,"children":482},{"className":481},[],[483],{"type":49,"value":315},{"type":49,"value":485}," will have four connections total: your DuckDB destination from Step 2 plus the three new sources.",{"type":43,"tag":189,"props":487,"children":488},{},[489],{"type":43,"tag":52,"props":490,"children":491},{},[492,497,499,504,506,512,514,521],{"type":43,"tag":77,"props":493,"children":494},{},[495],{"type":49,"value":496},"Security note.",{"type":49,"value":498}," ",{"type":43,"tag":310,"props":500,"children":502},{"className":501},[],[503],{"type":49,"value":315},{"type":49,"value":505}," is added to ",{"type":43,"tag":310,"props":507,"children":509},{"className":508},[],[510],{"type":49,"value":511},".gitignore",{"type":49,"value":513}," by default - your keys won't end up in git. If you're sharing the project, use environment variables instead; ",{"type":43,"tag":169,"props":515,"children":518},{"href":516,"rel":517},"https://getbruin.com/docs/bruin/secrets/overview.html",[173],[519],{"type":49,"value":520},"the docs",{"type":49,"value":522}," show how.",{"type":43,"tag":44,"props":524,"children":526},{"id":525},"_3-have-the-agent-write-three-ingestr-assets",[527],{"type":49,"value":528},"3. Have the agent write three ingestr assets",{"type":43,"tag":52,"props":530,"children":531},{},[532],{"type":49,"value":533},"Now the good part. Prompt:",{"type":43,"tag":301,"props":535,"children":536},{},[537,550,596,636,676],{"type":43,"tag":52,"props":538,"children":539},{},[540,542,548],{"type":49,"value":541},"In ",{"type":43,"tag":310,"props":543,"children":545},{"className":544},[],[546],{"type":49,"value":547},"marketing-analyst-101/assets/",{"type":49,"value":549},", create three ingestr assets using the ingestr documentation as reference:",{"type":43,"tag":52,"props":551,"children":552},{},[553,562,564,570,572,578,580,586,588,594],{"type":43,"tag":77,"props":554,"children":555},{},[556],{"type":43,"tag":310,"props":557,"children":559},{"className":558},[],[560],{"type":49,"value":561},"google_ads_campaigns.asset.yml",{"type":49,"value":563}," - pull campaign performance (campaign_id, campaign_name, date, impressions, clicks, cost_micros, conversions, conversion_value). Pull the last 90 days. Destination: ",{"type":43,"tag":310,"props":565,"children":567},{"className":566},[],[568],{"type":49,"value":569},"duckdb-default",{"type":49,"value":571},", schema ",{"type":43,"tag":310,"props":573,"children":575},{"className":574},[],[576],{"type":49,"value":577},"raw",{"type":49,"value":579},", table ",{"type":43,"tag":310,"props":581,"children":583},{"className":582},[],[584],{"type":49,"value":585},"google_ads_campaigns",{"type":49,"value":587},". Use merge strategy on ",{"type":43,"tag":310,"props":589,"children":591},{"className":590},[],[592],{"type":49,"value":593},"campaign_id + date",{"type":49,"value":595},".",{"type":43,"tag":52,"props":597,"children":598},{},[599,608,610,615,616,621,622,628,629,635],{"type":43,"tag":77,"props":600,"children":601},{},[602],{"type":43,"tag":310,"props":603,"children":605},{"className":604},[],[606],{"type":49,"value":607},"klaviyo_campaigns.asset.yml",{"type":49,"value":609}," - pull campaign-level email metrics (campaign_id, send_time, recipients, opens, clicks, unsubscribes, revenue). Pull the last 90 days. Destination: ",{"type":43,"tag":310,"props":611,"children":613},{"className":612},[],[614],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":617,"children":619},{"className":618},[],[620],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":623,"children":625},{"className":624},[],[626],{"type":49,"value":627},"klaviyo_campaigns",{"type":49,"value":587},{"type":43,"tag":310,"props":630,"children":632},{"className":631},[],[633],{"type":49,"value":634},"campaign_id",{"type":49,"value":595},{"type":43,"tag":52,"props":637,"children":638},{},[639,648,650,655,656,661,662,668,669,675],{"type":43,"tag":77,"props":640,"children":641},{},[642],{"type":43,"tag":310,"props":643,"children":645},{"className":644},[],[646],{"type":49,"value":647},"ga4_traffic.asset.yml",{"type":49,"value":649}," - pull daily sessions by source/medium (date, sessionSource, sessionMedium, sessions, totalUsers, conversions, purchaseRevenue). Pull the last 90 days. Destination: ",{"type":43,"tag":310,"props":651,"children":653},{"className":652},[],[654],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":657,"children":659},{"className":658},[],[660],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":663,"children":665},{"className":664},[],[666],{"type":49,"value":667},"ga4_traffic",{"type":49,"value":587},{"type":43,"tag":310,"props":670,"children":672},{"className":671},[],[673],{"type":49,"value":674},"date + sessionSource + sessionMedium",{"type":49,"value":595},{"type":43,"tag":52,"props":677,"children":678},{},[679],{"type":49,"value":680},"Before writing the files, show me each YAML and explain what it does. Don't run anything yet.",{"type":43,"tag":52,"props":682,"children":683},{},[684],{"type":49,"value":685},"The agent will produce three YAML files. Read them - they should look roughly like:",{"type":43,"tag":687,"props":688,"children":692},"pre",{"className":689,"code":690,"language":691,"meta":7,"style":7},"language-yaml shiki shiki-themes github-dark","name: raw.google_ads_campaigns\ntype: ingestr\nparameters:\n  source_connection: google-ads-default\n  source_table: \"campaign\"\n  destination: duckdb\n  destination_connection: duckdb-default\n  incremental_strategy: merge\n  incremental_key: \"campaign_id,date\"\n  interval_start: \"2026-01-24\"\n","yaml",[693],{"type":43,"tag":310,"props":694,"children":695},{"__ignoreMap":7},[696,719,737,750,768,786,804,822,840,858],{"type":43,"tag":412,"props":697,"children":700},{"class":698,"line":699},"line",1,[701,707,713],{"type":43,"tag":412,"props":702,"children":704},{"style":703},"--shiki-default:#85E89D",[705],{"type":49,"value":706},"name",{"type":43,"tag":412,"props":708,"children":710},{"style":709},"--shiki-default:#E1E4E8",[711],{"type":49,"value":712},": ",{"type":43,"tag":412,"props":714,"children":716},{"style":715},"--shiki-default:#9ECBFF",[717],{"type":49,"value":718},"raw.google_ads_campaigns\n",{"type":43,"tag":412,"props":720,"children":722},{"class":698,"line":721},2,[723,728,732],{"type":43,"tag":412,"props":724,"children":725},{"style":703},[726],{"type":49,"value":727},"type",{"type":43,"tag":412,"props":729,"children":730},{"style":709},[731],{"type":49,"value":712},{"type":43,"tag":412,"props":733,"children":734},{"style":715},[735],{"type":49,"value":736},"ingestr\n",{"type":43,"tag":412,"props":738,"children":739},{"class":698,"line":19},[740,745],{"type":43,"tag":412,"props":741,"children":742},{"style":703},[743],{"type":49,"value":744},"parameters",{"type":43,"tag":412,"props":746,"children":747},{"style":709},[748],{"type":49,"value":749},":\n",{"type":43,"tag":412,"props":751,"children":753},{"class":698,"line":752},4,[754,759,763],{"type":43,"tag":412,"props":755,"children":756},{"style":703},[757],{"type":49,"value":758},"  source_connection",{"type":43,"tag":412,"props":760,"children":761},{"style":709},[762],{"type":49,"value":712},{"type":43,"tag":412,"props":764,"children":765},{"style":715},[766],{"type":49,"value":767},"google-ads-default\n",{"type":43,"tag":412,"props":769,"children":771},{"class":698,"line":770},5,[772,777,781],{"type":43,"tag":412,"props":773,"children":774},{"style":703},[775],{"type":49,"value":776},"  source_table",{"type":43,"tag":412,"props":778,"children":779},{"style":709},[780],{"type":49,"value":712},{"type":43,"tag":412,"props":782,"children":783},{"style":715},[784],{"type":49,"value":785},"\"campaign\"\n",{"type":43,"tag":412,"props":787,"children":789},{"class":698,"line":788},6,[790,795,799],{"type":43,"tag":412,"props":791,"children":792},{"style":703},[793],{"type":49,"value":794},"  destination",{"type":43,"tag":412,"props":796,"children":797},{"style":709},[798],{"type":49,"value":712},{"type":43,"tag":412,"props":800,"children":801},{"style":715},[802],{"type":49,"value":803},"duckdb\n",{"type":43,"tag":412,"props":805,"children":807},{"class":698,"line":806},7,[808,813,817],{"type":43,"tag":412,"props":809,"children":810},{"style":703},[811],{"type":49,"value":812},"  destination_connection",{"type":43,"tag":412,"props":814,"children":815},{"style":709},[816],{"type":49,"value":712},{"type":43,"tag":412,"props":818,"children":819},{"style":715},[820],{"type":49,"value":821},"duckdb-default\n",{"type":43,"tag":412,"props":823,"children":825},{"class":698,"line":824},8,[826,831,835],{"type":43,"tag":412,"props":827,"children":828},{"style":703},[829],{"type":49,"value":830},"  incremental_strategy",{"type":43,"tag":412,"props":832,"children":833},{"style":709},[834],{"type":49,"value":712},{"type":43,"tag":412,"props":836,"children":837},{"style":715},[838],{"type":49,"value":839},"merge\n",{"type":43,"tag":412,"props":841,"children":843},{"class":698,"line":842},9,[844,849,853],{"type":43,"tag":412,"props":845,"children":846},{"style":703},[847],{"type":49,"value":848},"  incremental_key",{"type":43,"tag":412,"props":850,"children":851},{"style":709},[852],{"type":49,"value":712},{"type":43,"tag":412,"props":854,"children":855},{"style":715},[856],{"type":49,"value":857},"\"campaign_id,date\"\n",{"type":43,"tag":412,"props":859,"children":861},{"class":698,"line":860},10,[862,867,871],{"type":43,"tag":412,"props":863,"children":864},{"style":703},[865],{"type":49,"value":866},"  interval_start",{"type":43,"tag":412,"props":868,"children":869},{"style":709},[870],{"type":49,"value":712},{"type":43,"tag":412,"props":872,"children":873},{"style":715},[874],{"type":49,"value":875},"\"2026-01-24\"\n",{"type":43,"tag":52,"props":877,"children":878},{},[879,881,886,888,893],{"type":49,"value":880},"Field names vary by source - the ingestr docs and the ",{"type":43,"tag":169,"props":882,"children":883},{"href":34},[884],{"type":49,"value":885},"Shopify Data Pipeline ingest step",{"type":49,"value":887}," show real working examples for all three. Trust what the agent writes but glance at the YAML to make sure the ",{"type":43,"tag":77,"props":889,"children":890},{},[891],{"type":49,"value":892},"destination, schema, and table name",{"type":49,"value":894}," match what you asked for.",{"type":43,"tag":44,"props":896,"children":898},{"id":897},"_4-run-the-pipeline",[899],{"type":49,"value":900},"4. Run the pipeline",{"type":43,"tag":52,"props":902,"children":903},{},[904],{"type":49,"value":905},"One more prompt:",{"type":43,"tag":301,"props":907,"children":908},{},[909],{"type":43,"tag":52,"props":910,"children":911},{},[912,914,920],{"type":49,"value":913},"Run ",{"type":43,"tag":310,"props":915,"children":917},{"className":916},[],[918],{"type":49,"value":919},"bruin run marketing-analyst-101/",{"type":49,"value":921}," and stream the output. Tell me which assets succeeded and any that failed.",{"type":43,"tag":52,"props":923,"children":924},{},[925,927,933,935,941],{"type":49,"value":926},"This kicks off all three ingestions in parallel. First run typically takes 3–8 minutes depending on how much history each source returns. When it finishes, you'll have three tables in ",{"type":43,"tag":310,"props":928,"children":930},{"className":929},[],[931],{"type":49,"value":932},"raw.*",{"type":49,"value":934}," inside ",{"type":43,"tag":310,"props":936,"children":938},{"className":937},[],[939],{"type":49,"value":940},"marketing.duckdb",{"type":49,"value":595},{"type":43,"tag":44,"props":943,"children":945},{"id":944},"_5-peek-at-what-landed",[946],{"type":49,"value":947},"5. Peek at what landed",{"type":43,"tag":52,"props":949,"children":950},{},[951],{"type":49,"value":952},"Prompt:",{"type":43,"tag":301,"props":954,"children":955},{},[956,969],{"type":43,"tag":52,"props":957,"children":958},{},[959,961,967],{"type":49,"value":960},"Using ",{"type":43,"tag":310,"props":962,"children":964},{"className":963},[],[965],{"type":49,"value":966},"bruin query",{"type":49,"value":968},", show me:",{"type":43,"tag":327,"props":970,"children":971},{},[972,981,990],{"type":43,"tag":73,"props":973,"children":974},{},[975],{"type":43,"tag":310,"props":976,"children":978},{"className":977},[],[979],{"type":49,"value":980},"SELECT COUNT(*), MIN(date), MAX(date), SUM(cost_micros)/1e6 AS total_cost_usd FROM raw.google_ads_campaigns",{"type":43,"tag":73,"props":982,"children":983},{},[984],{"type":43,"tag":310,"props":985,"children":987},{"className":986},[],[988],{"type":49,"value":989},"SELECT COUNT(*), SUM(recipients), SUM(opens), SUM(revenue) FROM raw.klaviyo_campaigns",{"type":43,"tag":73,"props":991,"children":992},{},[993],{"type":43,"tag":310,"props":994,"children":996},{"className":995},[],[997],{"type":49,"value":998},"SELECT COUNT(*), SUM(sessions), SUM(conversions) FROM raw.ga4_traffic",{"type":43,"tag":52,"props":1000,"children":1001},{},[1002],{"type":49,"value":1003},"You should see ~90 days of data in each table. Sanity-check the totals against what you see in the native UI of each tool - they should match within a few percent (small discrepancies are normal due to timezone rounding).",{"type":43,"tag":44,"props":1005,"children":1007},{"id":1006},"troubleshooting",[1008],{"type":49,"value":1009},"Troubleshooting",{"type":43,"tag":69,"props":1011,"children":1012},{},[1013,1038,1048,1058],{"type":43,"tag":73,"props":1014,"children":1015},{},[1016,1021,1023,1029,1031,1037],{"type":43,"tag":77,"props":1017,"children":1018},{},[1019],{"type":49,"value":1020},"Google Ads \"Customer not found\"",{"type":49,"value":1022}," - the Customer ID needs no dashes. ",{"type":43,"tag":310,"props":1024,"children":1026},{"className":1025},[],[1027],{"type":49,"value":1028},"123-456-7890",{"type":49,"value":1030}," becomes ",{"type":43,"tag":310,"props":1032,"children":1034},{"className":1033},[],[1035],{"type":49,"value":1036},"1234567890",{"type":49,"value":595},{"type":43,"tag":73,"props":1039,"children":1040},{},[1041,1046],{"type":43,"tag":77,"props":1042,"children":1043},{},[1044],{"type":49,"value":1045},"Klaviyo \"401 Unauthorized\"",{"type":49,"value":1047}," - private API keys have scopes attached; make sure you granted reads for Campaigns, Flows, Metrics, Profiles.",{"type":43,"tag":73,"props":1049,"children":1050},{},[1051,1056],{"type":43,"tag":77,"props":1052,"children":1053},{},[1054],{"type":49,"value":1055},"GA4 \"Permission denied\"",{"type":49,"value":1057}," - the service account needs Viewer access on the GA4 property itself (not just the Google Cloud project).",{"type":43,"tag":73,"props":1059,"children":1060},{},[1061,1066],{"type":43,"tag":77,"props":1062,"children":1063},{},[1064],{"type":49,"value":1065},"DuckDB file locked",{"type":49,"value":1067}," - close any DuckDB desktop app that might have the file open.",{"type":43,"tag":44,"props":1069,"children":1071},{"id":1070},"what-just-happened",[1072],{"type":49,"value":1073},"What just happened",{"type":43,"tag":52,"props":1075,"children":1076},{},[1077,1079,1084],{"type":49,"value":1078},"You just built a three-source marketing data stack on a laptop, with zero SQL written by you. Every byte of it lives in ",{"type":43,"tag":310,"props":1080,"children":1082},{"className":1081},[],[1083],{"type":49,"value":940},{"type":49,"value":1085}," - one file, portable, inspectable with any SQL tool. Next step: teach the AI agent what these tables mean so it can reason about attribution and CAC accurately.",{"type":43,"tag":1087,"props":1088,"children":1089},"style",{},[1090],{"type":49,"value":1091},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":721,"depth":721,"links":1093},[1094,1095,1096,1097,1098,1099,1100,1101,1102],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":111,"depth":721,"text":114},{"id":291,"depth":721,"text":294},{"id":525,"depth":721,"text":528},{"id":897,"depth":721,"text":900},{"id":944,"depth":721,"text":947},{"id":1006,"depth":721,"text":1009},{"id":1070,"depth":721,"text":1073},"markdown","content:tutorials:marketing-analyst-101:ingest-data.md","content","tutorials/marketing-analyst-101/ingest-data.md","tutorials/marketing-analyst-101/ingest-data","md",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"readingTime":11,"category":12,"tags":1110,"difficulty":18,"module":5,"step":19,"journeys":1111,"roles":1112,"learnMore":1113,"author":1117,"body":1118,"_type":1103,"_id":1104,"_source":1105,"_file":1106,"_stem":1107,"_extension":1108},[14,15,16,17],[21],[23,24],[1114,1115,1116],{"label":27,"url":28},{"label":30,"url":31},{"label":33,"url":34},{"name":36,"role":37,"image":38},{"type":40,"children":1119,"toc":1952},[1120,1124,1128,1132,1136,1163,1167,1171,1305,1309,1313,1406,1452,1462,1491,1495,1499,1624,1628,1780,1794,1798,1802,1815,1831,1835,1839,1879,1883,1887,1934,1938,1948],{"type":43,"tag":44,"props":1121,"children":1122},{"id":46},[1123],{"type":49,"value":50},{"type":43,"tag":52,"props":1125,"children":1126},{},[1127],{"type":49,"value":56},{"type":43,"tag":44,"props":1129,"children":1130},{"id":59},[1131],{"type":49,"value":62},{"type":43,"tag":52,"props":1133,"children":1134},{},[1135],{"type":49,"value":67},{"type":43,"tag":69,"props":1137,"children":1138},{},[1139,1147,1155],{"type":43,"tag":73,"props":1140,"children":1141},{},[1142,1146],{"type":43,"tag":77,"props":1143,"children":1144},{},[1145],{"type":49,"value":81},{"type":49,"value":83},{"type":43,"tag":73,"props":1148,"children":1149},{},[1150,1154],{"type":43,"tag":77,"props":1151,"children":1152},{},[1153],{"type":49,"value":91},{"type":49,"value":93},{"type":43,"tag":73,"props":1156,"children":1157},{},[1158,1162],{"type":43,"tag":77,"props":1159,"children":1160},{},[1161],{"type":49,"value":101},{"type":49,"value":103},{"type":43,"tag":52,"props":1164,"children":1165},{},[1166],{"type":49,"value":108},{"type":43,"tag":44,"props":1168,"children":1169},{"id":111},[1170],{"type":49,"value":114},{"type":43,"tag":116,"props":1172,"children":1173},{":variants":118},[1174,1240,1266],{"type":43,"tag":121,"props":1175,"children":1176},{"v-slot:google-ads":7},[1177,1181,1185,1224],{"type":43,"tag":125,"props":1178,"children":1179},{"id":127},[1180],{"type":49,"value":81},{"type":43,"tag":52,"props":1182,"children":1183},{},[1184],{"type":49,"value":134},{"type":43,"tag":69,"props":1186,"children":1187},{},[1188,1196,1204],{"type":43,"tag":73,"props":1189,"children":1190},{},[1191,1195],{"type":43,"tag":77,"props":1192,"children":1193},{},[1194],{"type":49,"value":145},{"type":49,"value":147},{"type":43,"tag":73,"props":1197,"children":1198},{},[1199,1203],{"type":43,"tag":77,"props":1200,"children":1201},{},[1202],{"type":49,"value":155},{"type":49,"value":157},{"type":43,"tag":73,"props":1205,"children":1206},{},[1207,1211,1212,1217,1218,1223],{"type":43,"tag":77,"props":1208,"children":1209},{},[1210],{"type":49,"value":165},{"type":49,"value":167},{"type":43,"tag":169,"props":1213,"children":1215},{"href":171,"rel":1214},[173],[1216],{"type":49,"value":176},{"type":49,"value":178},{"type":43,"tag":169,"props":1219,"children":1221},{"href":181,"rel":1220},[173],[1222],{"type":49,"value":185},{"type":49,"value":187},{"type":43,"tag":189,"props":1225,"children":1226},{},[1227],{"type":43,"tag":52,"props":1228,"children":1229},{},[1230,1234,1235,1239],{"type":43,"tag":77,"props":1231,"children":1232},{},[1233],{"type":49,"value":199},{"type":49,"value":201},{"type":43,"tag":203,"props":1236,"children":1237},{},[1238],{"type":49,"value":207},{"type":49,"value":209},{"type":43,"tag":121,"props":1241,"children":1242},{"v-slot:klaviyo":7},[1243,1247,1251,1262],{"type":43,"tag":125,"props":1244,"children":1245},{"id":215},[1246],{"type":49,"value":91},{"type":43,"tag":52,"props":1248,"children":1249},{},[1250],{"type":49,"value":222},{"type":43,"tag":69,"props":1252,"children":1253},{},[1254],{"type":43,"tag":73,"props":1255,"children":1256},{},[1257,1261],{"type":43,"tag":77,"props":1258,"children":1259},{},[1260],{"type":49,"value":233},{"type":49,"value":235},{"type":43,"tag":52,"props":1263,"children":1264},{},[1265],{"type":49,"value":240},{"type":43,"tag":121,"props":1267,"children":1268},{"v-slot:ga4":7},[1269,1273,1277],{"type":43,"tag":125,"props":1270,"children":1271},{"id":246},[1272],{"type":49,"value":101},{"type":43,"tag":52,"props":1274,"children":1275},{},[1276],{"type":49,"value":253},{"type":43,"tag":69,"props":1278,"children":1279},{},[1280,1288,1296],{"type":43,"tag":73,"props":1281,"children":1282},{},[1283,1287],{"type":43,"tag":77,"props":1284,"children":1285},{},[1286],{"type":49,"value":264},{"type":49,"value":266},{"type":43,"tag":73,"props":1289,"children":1290},{},[1291,1295],{"type":43,"tag":77,"props":1292,"children":1293},{},[1294],{"type":49,"value":274},{"type":49,"value":276},{"type":43,"tag":73,"props":1297,"children":1298},{},[1299,1300,1304],{"type":49,"value":281},{"type":43,"tag":77,"props":1301,"children":1302},{},[1303],{"type":49,"value":286},{"type":49,"value":288},{"type":43,"tag":44,"props":1306,"children":1307},{"id":291},[1308],{"type":49,"value":294},{"type":43,"tag":52,"props":1310,"children":1311},{},[1312],{"type":49,"value":299},{"type":43,"tag":301,"props":1314,"children":1315},{},[1316,1332,1383,1399],{"type":43,"tag":52,"props":1317,"children":1318},{},[1319,1320,1325,1326,1331],{"type":49,"value":308},{"type":43,"tag":310,"props":1321,"children":1323},{"className":1322},[],[1324],{"type":49,"value":315},{"type":49,"value":317},{"type":43,"tag":310,"props":1327,"children":1329},{"className":1328},[],[1330],{"type":49,"value":323},{"type":49,"value":325},{"type":43,"tag":327,"props":1333,"children":1334},{},[1335,1351,1367],{"type":43,"tag":73,"props":1336,"children":1337},{},[1338,1339,1344,1345,1350],{"type":49,"value":334},{"type":43,"tag":310,"props":1340,"children":1342},{"className":1341},[],[1343],{"type":49,"value":340},{"type":49,"value":342},{"type":43,"tag":310,"props":1346,"children":1348},{"className":1347},[],[1349],{"type":49,"value":348},{"type":49,"value":350},{"type":43,"tag":73,"props":1352,"children":1353},{},[1354,1355,1360,1361,1366],{"type":49,"value":334},{"type":43,"tag":310,"props":1356,"children":1358},{"className":1357},[],[1359],{"type":49,"value":215},{"type":49,"value":342},{"type":43,"tag":310,"props":1362,"children":1364},{"className":1363},[],[1365],{"type":49,"value":366},{"type":49,"value":368},{"type":43,"tag":73,"props":1368,"children":1369},{},[1370,1371,1376,1377,1382],{"type":49,"value":334},{"type":43,"tag":310,"props":1372,"children":1374},{"className":1373},[],[1375],{"type":49,"value":378},{"type":49,"value":342},{"type":43,"tag":310,"props":1378,"children":1380},{"className":1379},[],[1381],{"type":49,"value":385},{"type":49,"value":387},{"type":43,"tag":52,"props":1384,"children":1385},{},[1386,1387,1392,1393,1398],{"type":49,"value":392},{"type":43,"tag":169,"props":1388,"children":1390},{"href":28,"rel":1389},[173],[1391],{"type":49,"value":28},{"type":49,"value":399},{"type":43,"tag":310,"props":1394,"children":1396},{"className":1395},[],[1397],{"type":49,"value":405},{"type":49,"value":407},{"type":43,"tag":52,"props":1400,"children":1401},{},[1402],{"type":43,"tag":412,"props":1403,"children":1404},{},[1405],{"type":49,"value":416},{"type":43,"tag":189,"props":1407,"children":1408},{},[1409],{"type":43,"tag":52,"props":1410,"children":1411},{},[1412,1416,1417,1422,1423,1427,1428,1433,1434,1439,1440,1445,1446,1451],{"type":43,"tag":77,"props":1413,"children":1414},{},[1415],{"type":49,"value":427},{"type":49,"value":429},{"type":43,"tag":310,"props":1418,"children":1420},{"className":1419},[],[1421],{"type":49,"value":315},{"type":49,"value":436},{"type":43,"tag":77,"props":1424,"children":1425},{},[1426],{"type":49,"value":441},{"type":49,"value":443},{"type":43,"tag":310,"props":1429,"children":1431},{"className":1430},[],[1432],{"type":49,"value":449},{"type":49,"value":451},{"type":43,"tag":310,"props":1435,"children":1437},{"className":1436},[],[1438],{"type":49,"value":457},{"type":49,"value":451},{"type":43,"tag":310,"props":1441,"children":1443},{"className":1442},[],[1444],{"type":49,"value":464},{"type":49,"value":466},{"type":43,"tag":310,"props":1447,"children":1449},{"className":1448},[],[1450],{"type":49,"value":315},{"type":49,"value":473},{"type":43,"tag":52,"props":1453,"children":1454},{},[1455,1456,1461],{"type":49,"value":478},{"type":43,"tag":310,"props":1457,"children":1459},{"className":1458},[],[1460],{"type":49,"value":315},{"type":49,"value":485},{"type":43,"tag":189,"props":1463,"children":1464},{},[1465],{"type":43,"tag":52,"props":1466,"children":1467},{},[1468,1472,1473,1478,1479,1484,1485,1490],{"type":43,"tag":77,"props":1469,"children":1470},{},[1471],{"type":49,"value":496},{"type":49,"value":498},{"type":43,"tag":310,"props":1474,"children":1476},{"className":1475},[],[1477],{"type":49,"value":315},{"type":49,"value":505},{"type":43,"tag":310,"props":1480,"children":1482},{"className":1481},[],[1483],{"type":49,"value":511},{"type":49,"value":513},{"type":43,"tag":169,"props":1486,"children":1488},{"href":516,"rel":1487},[173],[1489],{"type":49,"value":520},{"type":49,"value":522},{"type":43,"tag":44,"props":1492,"children":1493},{"id":525},[1494],{"type":49,"value":528},{"type":43,"tag":52,"props":1496,"children":1497},{},[1498],{"type":49,"value":533},{"type":43,"tag":301,"props":1500,"children":1501},{},[1502,1512,1548,1584,1620],{"type":43,"tag":52,"props":1503,"children":1504},{},[1505,1506,1511],{"type":49,"value":541},{"type":43,"tag":310,"props":1507,"children":1509},{"className":1508},[],[1510],{"type":49,"value":547},{"type":49,"value":549},{"type":43,"tag":52,"props":1513,"children":1514},{},[1515,1523,1524,1529,1530,1535,1536,1541,1542,1547],{"type":43,"tag":77,"props":1516,"children":1517},{},[1518],{"type":43,"tag":310,"props":1519,"children":1521},{"className":1520},[],[1522],{"type":49,"value":561},{"type":49,"value":563},{"type":43,"tag":310,"props":1525,"children":1527},{"className":1526},[],[1528],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":1531,"children":1533},{"className":1532},[],[1534],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":1537,"children":1539},{"className":1538},[],[1540],{"type":49,"value":585},{"type":49,"value":587},{"type":43,"tag":310,"props":1543,"children":1545},{"className":1544},[],[1546],{"type":49,"value":593},{"type":49,"value":595},{"type":43,"tag":52,"props":1549,"children":1550},{},[1551,1559,1560,1565,1566,1571,1572,1577,1578,1583],{"type":43,"tag":77,"props":1552,"children":1553},{},[1554],{"type":43,"tag":310,"props":1555,"children":1557},{"className":1556},[],[1558],{"type":49,"value":607},{"type":49,"value":609},{"type":43,"tag":310,"props":1561,"children":1563},{"className":1562},[],[1564],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":1567,"children":1569},{"className":1568},[],[1570],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":1573,"children":1575},{"className":1574},[],[1576],{"type":49,"value":627},{"type":49,"value":587},{"type":43,"tag":310,"props":1579,"children":1581},{"className":1580},[],[1582],{"type":49,"value":634},{"type":49,"value":595},{"type":43,"tag":52,"props":1585,"children":1586},{},[1587,1595,1596,1601,1602,1607,1608,1613,1614,1619],{"type":43,"tag":77,"props":1588,"children":1589},{},[1590],{"type":43,"tag":310,"props":1591,"children":1593},{"className":1592},[],[1594],{"type":49,"value":647},{"type":49,"value":649},{"type":43,"tag":310,"props":1597,"children":1599},{"className":1598},[],[1600],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":1603,"children":1605},{"className":1604},[],[1606],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":1609,"children":1611},{"className":1610},[],[1612],{"type":49,"value":667},{"type":49,"value":587},{"type":43,"tag":310,"props":1615,"children":1617},{"className":1616},[],[1618],{"type":49,"value":674},{"type":49,"value":595},{"type":43,"tag":52,"props":1621,"children":1622},{},[1623],{"type":49,"value":680},{"type":43,"tag":52,"props":1625,"children":1626},{},[1627],{"type":49,"value":685},{"type":43,"tag":687,"props":1629,"children":1630},{"className":689,"code":690,"language":691,"meta":7,"style":7},[1631],{"type":43,"tag":310,"props":1632,"children":1633},{"__ignoreMap":7},[1634,1649,1664,1675,1690,1705,1720,1735,1750,1765],{"type":43,"tag":412,"props":1635,"children":1636},{"class":698,"line":699},[1637,1641,1645],{"type":43,"tag":412,"props":1638,"children":1639},{"style":703},[1640],{"type":49,"value":706},{"type":43,"tag":412,"props":1642,"children":1643},{"style":709},[1644],{"type":49,"value":712},{"type":43,"tag":412,"props":1646,"children":1647},{"style":715},[1648],{"type":49,"value":718},{"type":43,"tag":412,"props":1650,"children":1651},{"class":698,"line":721},[1652,1656,1660],{"type":43,"tag":412,"props":1653,"children":1654},{"style":703},[1655],{"type":49,"value":727},{"type":43,"tag":412,"props":1657,"children":1658},{"style":709},[1659],{"type":49,"value":712},{"type":43,"tag":412,"props":1661,"children":1662},{"style":715},[1663],{"type":49,"value":736},{"type":43,"tag":412,"props":1665,"children":1666},{"class":698,"line":19},[1667,1671],{"type":43,"tag":412,"props":1668,"children":1669},{"style":703},[1670],{"type":49,"value":744},{"type":43,"tag":412,"props":1672,"children":1673},{"style":709},[1674],{"type":49,"value":749},{"type":43,"tag":412,"props":1676,"children":1677},{"class":698,"line":752},[1678,1682,1686],{"type":43,"tag":412,"props":1679,"children":1680},{"style":703},[1681],{"type":49,"value":758},{"type":43,"tag":412,"props":1683,"children":1684},{"style":709},[1685],{"type":49,"value":712},{"type":43,"tag":412,"props":1687,"children":1688},{"style":715},[1689],{"type":49,"value":767},{"type":43,"tag":412,"props":1691,"children":1692},{"class":698,"line":770},[1693,1697,1701],{"type":43,"tag":412,"props":1694,"children":1695},{"style":703},[1696],{"type":49,"value":776},{"type":43,"tag":412,"props":1698,"children":1699},{"style":709},[1700],{"type":49,"value":712},{"type":43,"tag":412,"props":1702,"children":1703},{"style":715},[1704],{"type":49,"value":785},{"type":43,"tag":412,"props":1706,"children":1707},{"class":698,"line":788},[1708,1712,1716],{"type":43,"tag":412,"props":1709,"children":1710},{"style":703},[1711],{"type":49,"value":794},{"type":43,"tag":412,"props":1713,"children":1714},{"style":709},[1715],{"type":49,"value":712},{"type":43,"tag":412,"props":1717,"children":1718},{"style":715},[1719],{"type":49,"value":803},{"type":43,"tag":412,"props":1721,"children":1722},{"class":698,"line":806},[1723,1727,1731],{"type":43,"tag":412,"props":1724,"children":1725},{"style":703},[1726],{"type":49,"value":812},{"type":43,"tag":412,"props":1728,"children":1729},{"style":709},[1730],{"type":49,"value":712},{"type":43,"tag":412,"props":1732,"children":1733},{"style":715},[1734],{"type":49,"value":821},{"type":43,"tag":412,"props":1736,"children":1737},{"class":698,"line":824},[1738,1742,1746],{"type":43,"tag":412,"props":1739,"children":1740},{"style":703},[1741],{"type":49,"value":830},{"type":43,"tag":412,"props":1743,"children":1744},{"style":709},[1745],{"type":49,"value":712},{"type":43,"tag":412,"props":1747,"children":1748},{"style":715},[1749],{"type":49,"value":839},{"type":43,"tag":412,"props":1751,"children":1752},{"class":698,"line":842},[1753,1757,1761],{"type":43,"tag":412,"props":1754,"children":1755},{"style":703},[1756],{"type":49,"value":848},{"type":43,"tag":412,"props":1758,"children":1759},{"style":709},[1760],{"type":49,"value":712},{"type":43,"tag":412,"props":1762,"children":1763},{"style":715},[1764],{"type":49,"value":857},{"type":43,"tag":412,"props":1766,"children":1767},{"class":698,"line":860},[1768,1772,1776],{"type":43,"tag":412,"props":1769,"children":1770},{"style":703},[1771],{"type":49,"value":866},{"type":43,"tag":412,"props":1773,"children":1774},{"style":709},[1775],{"type":49,"value":712},{"type":43,"tag":412,"props":1777,"children":1778},{"style":715},[1779],{"type":49,"value":875},{"type":43,"tag":52,"props":1781,"children":1782},{},[1783,1784,1788,1789,1793],{"type":49,"value":880},{"type":43,"tag":169,"props":1785,"children":1786},{"href":34},[1787],{"type":49,"value":885},{"type":49,"value":887},{"type":43,"tag":77,"props":1790,"children":1791},{},[1792],{"type":49,"value":892},{"type":49,"value":894},{"type":43,"tag":44,"props":1795,"children":1796},{"id":897},[1797],{"type":49,"value":900},{"type":43,"tag":52,"props":1799,"children":1800},{},[1801],{"type":49,"value":905},{"type":43,"tag":301,"props":1803,"children":1804},{},[1805],{"type":43,"tag":52,"props":1806,"children":1807},{},[1808,1809,1814],{"type":49,"value":913},{"type":43,"tag":310,"props":1810,"children":1812},{"className":1811},[],[1813],{"type":49,"value":919},{"type":49,"value":921},{"type":43,"tag":52,"props":1816,"children":1817},{},[1818,1819,1824,1825,1830],{"type":49,"value":926},{"type":43,"tag":310,"props":1820,"children":1822},{"className":1821},[],[1823],{"type":49,"value":932},{"type":49,"value":934},{"type":43,"tag":310,"props":1826,"children":1828},{"className":1827},[],[1829],{"type":49,"value":940},{"type":49,"value":595},{"type":43,"tag":44,"props":1832,"children":1833},{"id":944},[1834],{"type":49,"value":947},{"type":43,"tag":52,"props":1836,"children":1837},{},[1838],{"type":49,"value":952},{"type":43,"tag":301,"props":1840,"children":1841},{},[1842,1852],{"type":43,"tag":52,"props":1843,"children":1844},{},[1845,1846,1851],{"type":49,"value":960},{"type":43,"tag":310,"props":1847,"children":1849},{"className":1848},[],[1850],{"type":49,"value":966},{"type":49,"value":968},{"type":43,"tag":327,"props":1853,"children":1854},{},[1855,1863,1871],{"type":43,"tag":73,"props":1856,"children":1857},{},[1858],{"type":43,"tag":310,"props":1859,"children":1861},{"className":1860},[],[1862],{"type":49,"value":980},{"type":43,"tag":73,"props":1864,"children":1865},{},[1866],{"type":43,"tag":310,"props":1867,"children":1869},{"className":1868},[],[1870],{"type":49,"value":989},{"type":43,"tag":73,"props":1872,"children":1873},{},[1874],{"type":43,"tag":310,"props":1875,"children":1877},{"className":1876},[],[1878],{"type":49,"value":998},{"type":43,"tag":52,"props":1880,"children":1881},{},[1882],{"type":49,"value":1003},{"type":43,"tag":44,"props":1884,"children":1885},{"id":1006},[1886],{"type":49,"value":1009},{"type":43,"tag":69,"props":1888,"children":1889},{},[1890,1910,1918,1926],{"type":43,"tag":73,"props":1891,"children":1892},{},[1893,1897,1898,1903,1904,1909],{"type":43,"tag":77,"props":1894,"children":1895},{},[1896],{"type":49,"value":1020},{"type":49,"value":1022},{"type":43,"tag":310,"props":1899,"children":1901},{"className":1900},[],[1902],{"type":49,"value":1028},{"type":49,"value":1030},{"type":43,"tag":310,"props":1905,"children":1907},{"className":1906},[],[1908],{"type":49,"value":1036},{"type":49,"value":595},{"type":43,"tag":73,"props":1911,"children":1912},{},[1913,1917],{"type":43,"tag":77,"props":1914,"children":1915},{},[1916],{"type":49,"value":1045},{"type":49,"value":1047},{"type":43,"tag":73,"props":1919,"children":1920},{},[1921,1925],{"type":43,"tag":77,"props":1922,"children":1923},{},[1924],{"type":49,"value":1055},{"type":49,"value":1057},{"type":43,"tag":73,"props":1927,"children":1928},{},[1929,1933],{"type":43,"tag":77,"props":1930,"children":1931},{},[1932],{"type":49,"value":1065},{"type":49,"value":1067},{"type":43,"tag":44,"props":1935,"children":1936},{"id":1070},[1937],{"type":49,"value":1073},{"type":43,"tag":52,"props":1939,"children":1940},{},[1941,1942,1947],{"type":49,"value":1078},{"type":43,"tag":310,"props":1943,"children":1945},{"className":1944},[],[1946],{"type":49,"value":940},{"type":49,"value":1085},{"type":43,"tag":1087,"props":1949,"children":1950},{},[1951],{"type":49,"value":1091},{"title":7,"searchDepth":721,"depth":721,"links":1953},[1954,1955,1956,1957,1958,1959,1960,1961,1962],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":111,"depth":721,"text":114},{"id":291,"depth":721,"text":294},{"id":525,"depth":721,"text":528},{"id":897,"depth":721,"text":900},{"id":944,"depth":721,"text":947},{"id":1006,"depth":721,"text":1009},{"id":1070,"depth":721,"text":1073},[1964,2521,3488,4342,5377,6503],{"_path":1965,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1966,"description":1967,"date":10,"readingTime":824,"category":12,"tags":1968,"difficulty":18,"module":5,"step":699,"journeys":1970,"roles":1971,"learnMore":1972,"author":1979,"body":1980,"_type":1103,"_id":2518,"_source":1105,"_file":2519,"_stem":2520,"_extension":1108},"/tutorials/marketing-analyst-101/setup-workspace","Set Up Your Workspace","Install your AI coding tool, then have it install Bruin CLI and wire up MCP for you. Every step from here on is a plain-English prompt.",[14,15,1969,16],"MCP",[21],[23,24],[1973,1976],{"label":1974,"url":1975},"Bruin CLI installation","https://getbruin.com/docs/bruin/getting-started/introduction/installation.html",{"label":1977,"url":1978},"Bruin MCP setup","https://getbruin.com/docs/bruin/getting-started/bruin-mcp.html",{"name":36,"role":37,"image":38},{"type":40,"children":1981,"toc":2509},[1982,1986,2004,2009,2013,2018,2024,2029,2142,2148,2153,2185,2190,2210,2216,2221,2465,2471,2476,2484,2496,2500,2505],{"type":43,"tag":44,"props":1983,"children":1984},{"id":46},[1985],{"type":49,"value":50},{"type":43,"tag":327,"props":1987,"children":1988},{},[1989,1994,1999],{"type":43,"tag":73,"props":1990,"children":1991},{},[1992],{"type":49,"value":1993},"Install an AI coding tool (Claude Code, Cursor, or Codex) - the only thing you install yourself",{"type":43,"tag":73,"props":1995,"children":1996},{},[1997],{"type":49,"value":1998},"Have the agent install Bruin CLI on your laptop for you",{"type":43,"tag":73,"props":2000,"children":2001},{},[2002],{"type":49,"value":2003},"Wire the agent to Bruin via MCP so it can run Bruin commands on your behalf",{"type":43,"tag":52,"props":2005,"children":2006},{},[2007],{"type":49,"value":2008},"After this step you stop running terminal commands. You prompt the agent in plain English and it does the running.",{"type":43,"tag":44,"props":2010,"children":2011},{"id":59},[2012],{"type":49,"value":62},{"type":43,"tag":52,"props":2014,"children":2015},{},[2016],{"type":49,"value":2017},"Every step after this is a plain-English prompt - \"pull the last 90 days from Google Ads\", \"build a staging view\", \"what's our CAC by channel?\". The AI agent is the one typing commands, editing files, and running SQL. You're the analyst telling it what you want. This step installs the agent and gives it the keys to the workshop.",{"type":43,"tag":44,"props":2019,"children":2021},{"id":2020},"_1-install-your-ai-coding-tool",[2022],{"type":49,"value":2023},"1. Install your AI coding tool",{"type":43,"tag":52,"props":2025,"children":2026},{},[2027],{"type":49,"value":2028},"Pick one - any of these works. Claude Code is the fastest to get running.",{"type":43,"tag":116,"props":2030,"children":2032},{":variants":2031},"[{\"id\":\"claude-code\",\"label\":\"Claude Code\"},{\"id\":\"cursor\",\"label\":\"Cursor\"},{\"id\":\"codex\",\"label\":\"Codex\"}]",[2033,2097,2120],{"type":43,"tag":121,"props":2034,"children":2035},{"v-slot:claude-code":7},[2036,2042,2056,2084],{"type":43,"tag":125,"props":2037,"children":2039},{"id":2038},"claude-code",[2040],{"type":49,"value":2041},"Claude Code",{"type":43,"tag":52,"props":2043,"children":2044},{},[2045,2047,2054],{"type":49,"value":2046},"Follow the ",{"type":43,"tag":169,"props":2048,"children":2051},{"href":2049,"rel":2050},"https://docs.anthropic.com/en/docs/claude-code/overview",[173],[2052],{"type":49,"value":2053},"Claude Code installation guide",{"type":49,"value":2055},". On Mac it's one command:",{"type":43,"tag":687,"props":2057,"children":2061},{"className":2058,"code":2059,"language":2060,"meta":7,"style":7},"language-bash shiki shiki-themes github-dark","brew install claude\n","bash",[2062],{"type":43,"tag":310,"props":2063,"children":2064},{"__ignoreMap":7},[2065],{"type":43,"tag":412,"props":2066,"children":2067},{"class":698,"line":699},[2068,2074,2079],{"type":43,"tag":412,"props":2069,"children":2071},{"style":2070},"--shiki-default:#B392F0",[2072],{"type":49,"value":2073},"brew",{"type":43,"tag":412,"props":2075,"children":2076},{"style":715},[2077],{"type":49,"value":2078}," install",{"type":43,"tag":412,"props":2080,"children":2081},{"style":715},[2082],{"type":49,"value":2083}," claude\n",{"type":43,"tag":52,"props":2085,"children":2086},{},[2087,2089,2095],{"type":49,"value":2088},"Sign in once (",{"type":43,"tag":310,"props":2090,"children":2092},{"className":2091},[],[2093],{"type":49,"value":2094},"claude",{"type":49,"value":2096}," - it opens a browser) and you're ready.",{"type":43,"tag":121,"props":2098,"children":2099},{"v-slot:cursor":7},[2100,2106],{"type":43,"tag":125,"props":2101,"children":2103},{"id":2102},"cursor",[2104],{"type":49,"value":2105},"Cursor",{"type":43,"tag":52,"props":2107,"children":2108},{},[2109,2111,2118],{"type":49,"value":2110},"Download Cursor from ",{"type":43,"tag":169,"props":2112,"children":2115},{"href":2113,"rel":2114},"https://cursor.com/",[173],[2116],{"type":49,"value":2117},"cursor.com",{"type":49,"value":2119}," and sign in. Cursor is a full IDE - you'll open a folder in it for the rest of the tutorial.",{"type":43,"tag":121,"props":2121,"children":2122},{"v-slot:codex":7},[2123,2129],{"type":43,"tag":125,"props":2124,"children":2126},{"id":2125},"codex",[2127],{"type":49,"value":2128},"Codex",{"type":43,"tag":52,"props":2130,"children":2131},{},[2132,2133,2140],{"type":49,"value":2046},{"type":43,"tag":169,"props":2134,"children":2137},{"href":2135,"rel":2136},"https://openai.com/index/introducing-codex/",[173],[2138],{"type":49,"value":2139},"Codex installation instructions",{"type":49,"value":2141},". Sign in with your OpenAI account.",{"type":43,"tag":44,"props":2143,"children":2145},{"id":2144},"_2-have-the-agent-install-bruin-cli",[2146],{"type":49,"value":2147},"2. Have the agent install Bruin CLI",{"type":43,"tag":52,"props":2149,"children":2150},{},[2151],{"type":49,"value":2152},"Open your AI tool in any folder you'd like to use as your workspace, and prompt:",{"type":43,"tag":301,"props":2154,"children":2155},{},[2156],{"type":43,"tag":52,"props":2157,"children":2158},{},[2159,2161,2167,2169,2175,2177,2183],{"type":49,"value":2160},"Install the Bruin CLI on this machine by running ",{"type":43,"tag":310,"props":2162,"children":2164},{"className":2163},[],[2165],{"type":49,"value":2166},"curl -LsSf https://getbruin.com/install/cli | sh",{"type":49,"value":2168},", then verify it worked with ",{"type":43,"tag":310,"props":2170,"children":2172},{"className":2171},[],[2173],{"type":49,"value":2174},"bruin --version",{"type":49,"value":2176},". If ",{"type":43,"tag":310,"props":2178,"children":2180},{"className":2179},[],[2181],{"type":49,"value":2182},"bruin",{"type":49,"value":2184}," isn't on my PATH after install, tell me what to add or which terminal to open.",{"type":43,"tag":52,"props":2186,"children":2187},{},[2188],{"type":49,"value":2189},"The agent will ask for permission to run a shell command - approve it. (You'll see this prompt a lot in the steps that follow; you're authorizing the agent to do the work for you.) When it finishes, the agent should print back a Bruin version number.",{"type":43,"tag":189,"props":2191,"children":2192},{},[2193],{"type":43,"tag":52,"props":2194,"children":2195},{},[2196,2201,2203,2208],{"type":43,"tag":77,"props":2197,"children":2198},{},[2199],{"type":49,"value":2200},"If your AI tool can't run terminal commands",{"type":49,"value":2202},", paste the install command into any terminal yourself: ",{"type":43,"tag":310,"props":2204,"children":2206},{"className":2205},[],[2207],{"type":49,"value":2166},{"type":49,"value":2209},". The rest of the tutorial works the same.",{"type":43,"tag":44,"props":2211,"children":2213},{"id":2212},"_3-add-bruin-as-an-mcp-server",[2214],{"type":49,"value":2215},"3. Add Bruin as an MCP server",{"type":43,"tag":52,"props":2217,"children":2218},{},[2219],{"type":49,"value":2220},"MCP (Model Context Protocol) is the bridge that lets your AI tool call Bruin commands directly. Once it's wired up, the agent can ingest, query, validate - all of it - through prompts.",{"type":43,"tag":116,"props":2222,"children":2223},{":variants":2031},[2224,2262,2394],{"type":43,"tag":121,"props":2225,"children":2226},{"v-slot:claude-code":7},[2227,2232,2236,2252],{"type":43,"tag":125,"props":2228,"children":2230},{"id":2229},"claude-code-1",[2231],{"type":49,"value":2041},{"type":43,"tag":52,"props":2233,"children":2234},{},[2235],{"type":49,"value":299},{"type":43,"tag":301,"props":2237,"children":2238},{},[2239],{"type":43,"tag":52,"props":2240,"children":2241},{},[2242,2244,2250],{"type":49,"value":2243},"Register Bruin as an MCP server by running ",{"type":43,"tag":310,"props":2245,"children":2247},{"className":2246},[],[2248],{"type":49,"value":2249},"claude mcp add bruin -- bruin mcp",{"type":49,"value":2251},". Then remind me to restart Claude Code so the change takes effect.",{"type":43,"tag":52,"props":2253,"children":2254},{},[2255,2260],{"type":43,"tag":77,"props":2256,"children":2257},{},[2258],{"type":49,"value":2259},"Important:",{"type":49,"value":2261}," close your current Claude Code session and start a new one before moving on.",{"type":43,"tag":121,"props":2263,"children":2264},{"v-slot:cursor":7},[2265,2270,2275,2313,2386],{"type":43,"tag":125,"props":2266,"children":2268},{"id":2267},"cursor-1",[2269],{"type":49,"value":2105},{"type":43,"tag":52,"props":2271,"children":2272},{},[2273],{"type":49,"value":2274},"Cursor's MCP setup is a UI step - the agent can't do this one for you:",{"type":43,"tag":327,"props":2276,"children":2277},{},[2278,2298,2308],{"type":43,"tag":73,"props":2279,"children":2280},{},[2281,2283,2288,2290,2296],{"type":49,"value":2282},"Open ",{"type":43,"tag":77,"props":2284,"children":2285},{},[2286],{"type":49,"value":2287},"Cursor Settings",{"type":49,"value":2289}," (",{"type":43,"tag":310,"props":2291,"children":2293},{"className":2292},[],[2294],{"type":49,"value":2295},"Cmd/Ctrl + ,",{"type":49,"value":2297},")",{"type":43,"tag":73,"props":2299,"children":2300},{},[2301,2303],{"type":49,"value":2302},"Go to ",{"type":43,"tag":77,"props":2304,"children":2305},{},[2306],{"type":49,"value":2307},"MCP & Integrations → Add Custom MCP",{"type":43,"tag":73,"props":2309,"children":2310},{},[2311],{"type":49,"value":2312},"Paste:",{"type":43,"tag":687,"props":2314,"children":2318},{"className":2315,"code":2316,"language":2317,"meta":7,"style":7},"language-json shiki shiki-themes github-dark","{\n  \"mcpServers\": {\n    \"bruin\": {\n      \"command\": \"bruin\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n","json",[2319],{"type":43,"tag":310,"props":2320,"children":2321},{"__ignoreMap":7},[2322,2330,2338,2346,2354,2362,2370,2378],{"type":43,"tag":412,"props":2323,"children":2324},{"class":698,"line":699},[2325],{"type":43,"tag":412,"props":2326,"children":2327},{},[2328],{"type":49,"value":2329},"{\n",{"type":43,"tag":412,"props":2331,"children":2332},{"class":698,"line":721},[2333],{"type":43,"tag":412,"props":2334,"children":2335},{},[2336],{"type":49,"value":2337},"  \"mcpServers\": {\n",{"type":43,"tag":412,"props":2339,"children":2340},{"class":698,"line":19},[2341],{"type":43,"tag":412,"props":2342,"children":2343},{},[2344],{"type":49,"value":2345},"    \"bruin\": {\n",{"type":43,"tag":412,"props":2347,"children":2348},{"class":698,"line":752},[2349],{"type":43,"tag":412,"props":2350,"children":2351},{},[2352],{"type":49,"value":2353},"      \"command\": \"bruin\",\n",{"type":43,"tag":412,"props":2355,"children":2356},{"class":698,"line":770},[2357],{"type":43,"tag":412,"props":2358,"children":2359},{},[2360],{"type":49,"value":2361},"      \"args\": [\"mcp\"]\n",{"type":43,"tag":412,"props":2363,"children":2364},{"class":698,"line":788},[2365],{"type":43,"tag":412,"props":2366,"children":2367},{},[2368],{"type":49,"value":2369},"    }\n",{"type":43,"tag":412,"props":2371,"children":2372},{"class":698,"line":806},[2373],{"type":43,"tag":412,"props":2374,"children":2375},{},[2376],{"type":49,"value":2377},"  }\n",{"type":43,"tag":412,"props":2379,"children":2380},{"class":698,"line":824},[2381],{"type":43,"tag":412,"props":2382,"children":2383},{},[2384],{"type":49,"value":2385},"}\n",{"type":43,"tag":327,"props":2387,"children":2388},{"start":752},[2389],{"type":43,"tag":73,"props":2390,"children":2391},{},[2392],{"type":49,"value":2393},"Restart Cursor.",{"type":43,"tag":121,"props":2395,"children":2396},{"v-slot:codex":7},[2397,2402,2406,2460],{"type":43,"tag":125,"props":2398,"children":2400},{"id":2399},"codex-1",[2401],{"type":49,"value":2128},{"type":43,"tag":52,"props":2403,"children":2404},{},[2405],{"type":49,"value":299},{"type":43,"tag":301,"props":2407,"children":2408},{},[2409,2422,2455],{"type":43,"tag":52,"props":2410,"children":2411},{},[2412,2414,2420],{"type":49,"value":2413},"Add a Bruin MCP server entry to ",{"type":43,"tag":310,"props":2415,"children":2417},{"className":2416},[],[2418],{"type":49,"value":2419},"~/.codex/config.toml",{"type":49,"value":2421}," so I can call Bruin from this tool. The block should be:",{"type":43,"tag":687,"props":2423,"children":2427},{"className":2424,"code":2425,"language":2426,"meta":7,"style":7},"language-toml shiki shiki-themes github-dark","[mcp_servers.bruin]\ncommand = \"bruin\"\nargs = [\"mcp\"]\n","toml",[2428],{"type":43,"tag":310,"props":2429,"children":2430},{"__ignoreMap":7},[2431,2439,2447],{"type":43,"tag":412,"props":2432,"children":2433},{"class":698,"line":699},[2434],{"type":43,"tag":412,"props":2435,"children":2436},{},[2437],{"type":49,"value":2438},"[mcp_servers.bruin]\n",{"type":43,"tag":412,"props":2440,"children":2441},{"class":698,"line":721},[2442],{"type":43,"tag":412,"props":2443,"children":2444},{},[2445],{"type":49,"value":2446},"command = \"bruin\"\n",{"type":43,"tag":412,"props":2448,"children":2449},{"class":698,"line":19},[2450],{"type":43,"tag":412,"props":2451,"children":2452},{},[2453],{"type":49,"value":2454},"args = [\"mcp\"]\n",{"type":43,"tag":52,"props":2456,"children":2457},{},[2458],{"type":49,"value":2459},"Then remind me to restart Codex.",{"type":43,"tag":52,"props":2461,"children":2462},{},[2463],{"type":49,"value":2464},"After it confirms, restart Codex.",{"type":43,"tag":44,"props":2466,"children":2468},{"id":2467},"_4-verify-the-wiring",[2469],{"type":49,"value":2470},"4. Verify the wiring",{"type":43,"tag":52,"props":2472,"children":2473},{},[2474],{"type":49,"value":2475},"Open your AI tool and prompt:",{"type":43,"tag":301,"props":2477,"children":2478},{},[2479],{"type":43,"tag":52,"props":2480,"children":2481},{},[2482],{"type":49,"value":2483},"List the Bruin connections I have configured.",{"type":43,"tag":52,"props":2485,"children":2486},{},[2487,2489,2494],{"type":49,"value":2488},"The agent should call Bruin, get back an empty list (you haven't added any connections yet), and report that. ",{"type":43,"tag":77,"props":2490,"children":2491},{},[2492],{"type":49,"value":2493},"You want the agent to actually run the command",{"type":49,"value":2495}," - not just guess. If it replies with generic text like \"I don't know, can you check?\", MCP isn't wired. Go back and double-check the setup above.",{"type":43,"tag":44,"props":2497,"children":2498},{"id":1070},[2499],{"type":49,"value":1073},{"type":43,"tag":52,"props":2501,"children":2502},{},[2503],{"type":49,"value":2504},"You have the full toolchain on your laptop: an AI brain (Claude/Cursor/Codex), a data engine (Bruin), and a bridge between them (MCP). From here on out, every step is \"tell the agent what you want, the agent runs the command.\" You won't open a terminal yourself again unless you want to.",{"type":43,"tag":1087,"props":2506,"children":2507},{},[2508],{"type":49,"value":1091},{"title":7,"searchDepth":721,"depth":721,"links":2510},[2511,2512,2513,2514,2515,2516,2517],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":2020,"depth":721,"text":2023},{"id":2144,"depth":721,"text":2147},{"id":2212,"depth":721,"text":2215},{"id":2467,"depth":721,"text":2470},{"id":1070,"depth":721,"text":1073},"content:tutorials:marketing-analyst-101:setup-workspace.md","tutorials/marketing-analyst-101/setup-workspace.md","tutorials/marketing-analyst-101/setup-workspace",{"_path":2522,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":2523,"description":2524,"date":10,"readingTime":770,"category":12,"tags":2525,"difficulty":18,"module":5,"step":721,"journeys":2526,"roles":2527,"learnMore":2528,"author":2538,"body":2539,"_type":1103,"_id":3485,"_source":1105,"_file":3486,"_stem":3487,"_extension":1108},"/tutorials/marketing-analyst-101/scaffold-template","Scaffold Your Project","Create an empty Bruin project with DuckDB as the local database - the workspace your ad, email, and web analytics data will land into.",[14,15,16],[21],[23,24],[2529,2532,2535],{"label":2530,"url":2531},"Bruin init command","https://getbruin.com/docs/bruin/commands/init.html",{"label":2533,"url":2534},"Bruin templates tutorial","/learn/bruin-templates",{"label":2536,"url":2537},"DuckDB platform docs","https://getbruin.com/docs/bruin/platforms/duckdb.html",{"name":36,"role":37,"image":38},{"type":40,"children":2540,"toc":3474},[2541,2545,2565,2569,2574,2625,2630,2636,2641,2680,2685,2727,2733,2738,2748,2753,2769,2780,2792,2885,2890,2903,2922,2926,3427,3454,3458,3470],{"type":43,"tag":44,"props":2542,"children":2543},{"id":46},[2544],{"type":49,"value":50},{"type":43,"tag":52,"props":2546,"children":2547},{},[2548,2550,2555,2557,2563],{"type":49,"value":2549},"Ask your AI agent to create a Bruin project called ",{"type":43,"tag":310,"props":2551,"children":2553},{"className":2552},[],[2554],{"type":49,"value":5},{"type":49,"value":2556}," with a local DuckDB database as the destination, then seed an ",{"type":43,"tag":310,"props":2558,"children":2560},{"className":2559},[],[2561],{"type":49,"value":2562},"AGENTS.md",{"type":49,"value":2564}," file at the project root with the Bruin rules the agent should follow in every future session.",{"type":43,"tag":44,"props":2566,"children":2567},{"id":59},[2568],{"type":49,"value":62},{"type":43,"tag":52,"props":2570,"children":2571},{},[2572],{"type":49,"value":2573},"A Bruin project is a folder on your laptop that holds:",{"type":43,"tag":69,"props":2575,"children":2576},{},[2577,2594,2606],{"type":43,"tag":73,"props":2578,"children":2579},{},[2580,2581,2586,2587,2592],{"type":49,"value":334},{"type":43,"tag":77,"props":2582,"children":2583},{},[2584],{"type":49,"value":2585},"config file",{"type":49,"value":2289},{"type":43,"tag":310,"props":2588,"children":2590},{"className":2589},[],[2591],{"type":49,"value":315},{"type":49,"value":2593},") - where your API keys and database connection live",{"type":43,"tag":73,"props":2595,"children":2596},{},[2597,2599,2604],{"type":49,"value":2598},"An ",{"type":43,"tag":77,"props":2600,"children":2601},{},[2602],{"type":49,"value":2603},"assets folder",{"type":49,"value":2605}," - where the rules for pulling data will go (Google Ads, Klaviyo, GA4 assets)",{"type":43,"tag":73,"props":2607,"children":2608},{},[2609,2610,2615,2617,2623],{"type":49,"value":334},{"type":43,"tag":77,"props":2611,"children":2612},{},[2613],{"type":49,"value":2614},"DuckDB file",{"type":49,"value":2616}," - the database itself, which is just a single ",{"type":43,"tag":310,"props":2618,"children":2620},{"className":2619},[],[2621],{"type":49,"value":2622},".duckdb",{"type":49,"value":2624}," file on your disk",{"type":43,"tag":52,"props":2626,"children":2627},{},[2628],{"type":49,"value":2629},"DuckDB is the key choice here. It's a database that lives entirely in a file - no server to run, no cloud account to create, no credit card. It behaves like Postgres or BigQuery but all the data sits on your laptop. Perfect for learning, perfect for ad-hoc marketing analysis without going through the data team.",{"type":43,"tag":44,"props":2631,"children":2633},{"id":2632},"prompt-the-agent",[2634],{"type":49,"value":2635},"Prompt the agent",{"type":43,"tag":52,"props":2637,"children":2638},{},[2639],{"type":49,"value":2640},"Open your AI coding tool in an empty folder you'd like to use as your workspace, and paste this prompt:",{"type":43,"tag":301,"props":2642,"children":2643},{},[2644],{"type":43,"tag":52,"props":2645,"children":2646},{},[2647,2649,2655,2657,2662,2664,2670,2672,2678],{"type":49,"value":2648},"Using Bruin MCP, run ",{"type":43,"tag":310,"props":2650,"children":2652},{"className":2651},[],[2653],{"type":49,"value":2654},"bruin init empty marketing-analyst-101",{"type":49,"value":2656}," to scaffold a new pipeline from the empty template. Then add a DuckDB connection called ",{"type":43,"tag":310,"props":2658,"children":2660},{"className":2659},[],[2661],{"type":49,"value":569},{"type":49,"value":2663}," pointing to ",{"type":43,"tag":310,"props":2665,"children":2667},{"className":2666},[],[2668],{"type":49,"value":2669},"./marketing-analyst-101/marketing.duckdb",{"type":49,"value":2671},". Finally, run ",{"type":43,"tag":310,"props":2673,"children":2675},{"className":2674},[],[2676],{"type":49,"value":2677},"bruin connections test --name duckdb-default",{"type":49,"value":2679}," and show me the output.",{"type":43,"tag":52,"props":2681,"children":2682},{},[2683],{"type":49,"value":2684},"The agent will:",{"type":43,"tag":327,"props":2686,"children":2687},{},[2688,2697,2722],{"type":43,"tag":73,"props":2689,"children":2690},{},[2691,2692],{"type":49,"value":913},{"type":43,"tag":310,"props":2693,"children":2695},{"className":2694},[],[2696],{"type":49,"value":2654},{"type":43,"tag":73,"props":2698,"children":2699},{},[2700,2702,2707,2709,2715,2717],{"type":49,"value":2701},"Edit ",{"type":43,"tag":310,"props":2703,"children":2705},{"className":2704},[],[2706],{"type":49,"value":315},{"type":49,"value":2708}," to add a ",{"type":43,"tag":310,"props":2710,"children":2712},{"className":2711},[],[2713],{"type":49,"value":2714},"duckdb",{"type":49,"value":2716}," connection block pointing at ",{"type":43,"tag":310,"props":2718,"children":2720},{"className":2719},[],[2721],{"type":49,"value":2669},{"type":43,"tag":73,"props":2723,"children":2724},{},[2725],{"type":49,"value":2726},"Run the test command to confirm the connection works",{"type":43,"tag":44,"props":2728,"children":2730},{"id":2729},"what-the-agent-just-created",[2731],{"type":49,"value":2732},"What the agent just created",{"type":43,"tag":52,"props":2734,"children":2735},{},[2736],{"type":49,"value":2737},"Your folder now looks like this:",{"type":43,"tag":687,"props":2739,"children":2743},{"className":2740,"code":2742,"language":49},[2741],"language-text","./                                # current folder\n├── .bruin.yml                    # project config - holds API keys + connections\n└── marketing-analyst-101/        # the pipeline\n    ├── pipeline.yml              # pipeline config\n    ├── assets/                   # where ingestion rules will live (empty for now)\n    │   └── empty.sql             # placeholder - safe to delete\n    └── marketing.duckdb          # the database (created on first write)\n",[2744],{"type":43,"tag":310,"props":2745,"children":2746},{"__ignoreMap":7},[2747],{"type":49,"value":2742},{"type":43,"tag":52,"props":2749,"children":2750},{},[2751],{"type":49,"value":2752},"Ask the agent to delete the placeholder:",{"type":43,"tag":301,"props":2754,"children":2755},{},[2756],{"type":43,"tag":52,"props":2757,"children":2758},{},[2759,2761,2767],{"type":49,"value":2760},"Remove ",{"type":43,"tag":310,"props":2762,"children":2764},{"className":2763},[],[2765],{"type":49,"value":2766},"marketing-analyst-101/assets/empty.sql",{"type":49,"value":2768}," - I'll add real assets next step.",{"type":43,"tag":44,"props":2770,"children":2772},{"id":2771},"peek-inside-bruinyml",[2773,2775],{"type":49,"value":2774},"Peek inside ",{"type":43,"tag":310,"props":2776,"children":2778},{"className":2777},[],[2779],{"type":49,"value":315},{"type":43,"tag":52,"props":2781,"children":2782},{},[2783,2785,2790],{"type":49,"value":2784},"Have the agent show you ",{"type":43,"tag":310,"props":2786,"children":2788},{"className":2787},[],[2789],{"type":49,"value":315},{"type":49,"value":2791},". You should see something like:",{"type":43,"tag":687,"props":2793,"children":2795},{"className":689,"code":2794,"language":691,"meta":7,"style":7},"environments:\n  default:\n    connections:\n      duckdb:\n        - name: \"duckdb-default\"\n          path: \"./marketing-analyst-101/marketing.duckdb\"\n",[2796],{"type":43,"tag":310,"props":2797,"children":2798},{"__ignoreMap":7},[2799,2811,2823,2835,2847,2868],{"type":43,"tag":412,"props":2800,"children":2801},{"class":698,"line":699},[2802,2807],{"type":43,"tag":412,"props":2803,"children":2804},{"style":703},[2805],{"type":49,"value":2806},"environments",{"type":43,"tag":412,"props":2808,"children":2809},{"style":709},[2810],{"type":49,"value":749},{"type":43,"tag":412,"props":2812,"children":2813},{"class":698,"line":721},[2814,2819],{"type":43,"tag":412,"props":2815,"children":2816},{"style":703},[2817],{"type":49,"value":2818},"  default",{"type":43,"tag":412,"props":2820,"children":2821},{"style":709},[2822],{"type":49,"value":749},{"type":43,"tag":412,"props":2824,"children":2825},{"class":698,"line":19},[2826,2831],{"type":43,"tag":412,"props":2827,"children":2828},{"style":703},[2829],{"type":49,"value":2830},"    connections",{"type":43,"tag":412,"props":2832,"children":2833},{"style":709},[2834],{"type":49,"value":749},{"type":43,"tag":412,"props":2836,"children":2837},{"class":698,"line":752},[2838,2843],{"type":43,"tag":412,"props":2839,"children":2840},{"style":703},[2841],{"type":49,"value":2842},"      duckdb",{"type":43,"tag":412,"props":2844,"children":2845},{"style":709},[2846],{"type":49,"value":749},{"type":43,"tag":412,"props":2848,"children":2849},{"class":698,"line":770},[2850,2855,2859,2863],{"type":43,"tag":412,"props":2851,"children":2852},{"style":709},[2853],{"type":49,"value":2854},"        - ",{"type":43,"tag":412,"props":2856,"children":2857},{"style":703},[2858],{"type":49,"value":706},{"type":43,"tag":412,"props":2860,"children":2861},{"style":709},[2862],{"type":49,"value":712},{"type":43,"tag":412,"props":2864,"children":2865},{"style":715},[2866],{"type":49,"value":2867},"\"duckdb-default\"\n",{"type":43,"tag":412,"props":2869,"children":2870},{"class":698,"line":788},[2871,2876,2880],{"type":43,"tag":412,"props":2872,"children":2873},{"style":703},[2874],{"type":49,"value":2875},"          path",{"type":43,"tag":412,"props":2877,"children":2878},{"style":709},[2879],{"type":49,"value":712},{"type":43,"tag":412,"props":2881,"children":2882},{"style":715},[2883],{"type":49,"value":2884},"\"./marketing-analyst-101/marketing.duckdb\"\n",{"type":43,"tag":52,"props":2886,"children":2887},{},[2888],{"type":49,"value":2889},"This file is your control panel. Every API key and OAuth token you add (Google Ads, Klaviyo, GA4) will live here too. It's local, git-ignored by default, and never leaves your machine.",{"type":43,"tag":44,"props":2891,"children":2893},{"id":2892},"seed-agentsmd-with-bruin-rules",[2894,2896,2901],{"type":49,"value":2895},"Seed ",{"type":43,"tag":310,"props":2897,"children":2899},{"className":2898},[],[2900],{"type":49,"value":2562},{"type":49,"value":2902}," with Bruin rules",{"type":43,"tag":52,"props":2904,"children":2905},{},[2906,2908,2913,2915,2920],{"type":49,"value":2907},"Any AI coding tool (Claude Code, Cursor, Codex) automatically reads an ",{"type":43,"tag":310,"props":2909,"children":2911},{"className":2910},[],[2912],{"type":49,"value":2562},{"type":49,"value":2914}," at the project root whenever the workspace opens. This is where you tell the agent ",{"type":43,"tag":203,"props":2916,"children":2917},{},[2918],{"type":49,"value":2919},"how to work",{"type":49,"value":2921}," in this project - before it ever touches your data.",{"type":43,"tag":52,"props":2923,"children":2924},{},[2925],{"type":49,"value":299},{"type":43,"tag":301,"props":2927,"children":2928},{},[2929,2979],{"type":43,"tag":52,"props":2930,"children":2931},{},[2932,2934,2939,2941,2946,2948,2954,2956,2962,2964,2969,2971,2977],{"type":49,"value":2933},"Create an ",{"type":43,"tag":310,"props":2935,"children":2937},{"className":2936},[],[2938],{"type":49,"value":2562},{"type":49,"value":2940}," at the root of this workspace (next to ",{"type":43,"tag":310,"props":2942,"children":2944},{"className":2943},[],[2945],{"type":49,"value":315},{"type":49,"value":2947},") with the content below, then show me the file after creation. Also ask me whether I want you to scaffold separate ",{"type":43,"tag":310,"props":2949,"children":2951},{"className":2950},[],[2952],{"type":49,"value":2953},"dev",{"type":49,"value":2955}," and ",{"type":43,"tag":310,"props":2957,"children":2959},{"className":2958},[],[2960],{"type":49,"value":2961},"prod",{"type":49,"value":2963}," environments in ",{"type":43,"tag":310,"props":2965,"children":2967},{"className":2966},[],[2968],{"type":49,"value":315},{"type":49,"value":2970},", or keep a single ",{"type":43,"tag":310,"props":2972,"children":2974},{"className":2973},[],[2975],{"type":49,"value":2976},"default",{"type":49,"value":2978}," environment for now - wait for my answer before making any changes to environments.",{"type":43,"tag":687,"props":2980,"children":2983},{"className":2981,"code":2982,"language":1103,"meta":7,"style":7},"language-markdown shiki shiki-themes github-dark","# AGENTS.md\n\n## How you (the AI agent) should work in this project\n\n### Ground rules\n- This is a Bruin project. Use the **Bruin MCP** tools when available - they're the fastest, most reliable path\n- Use the **Bruin CLI** for all pipeline operations: `bruin init`, `bruin run`, `bruin validate`, `bruin query`, `bruin ai enhance`, `bruin connections test`\n- Reference the **[Bruin docs](https://getbruin.com/docs)** when unsure about asset types, materializations, connection configs, or CLI flags. Don't guess - the docs are authoritative\n- **Ask me when unclear.** If a request is ambiguous (time range, columns, grain, attribution model, which environment, etc.), ask before guessing. A clarifying question now beats a wrong answer later\n\n### Environments\n- Before adding or changing connections, confirm with me whether this project uses a single `default` environment or separate `dev` / `prod` environments\n- Never copy secrets between environments without asking\n- Keep env-specific values (DB paths, API keys, schemas) scoped to the right environment block\n\n### Cap data volume when testing\n- For exploratory queries, use `LIMIT 20` (or fewer) until you've confirmed the shape of the result\n- For ingestr assets during testing, use Bruin's **interval dates** (`interval_start`, `interval_end`) to cap the backfill window - never pull unbounded history on a first run\n- Prefer **narrow, explicit date windows** (e.g. last 7 days) over open-ended scans\n\n### Validate before you run\n- Run `bruin validate \u003Cpath>` before `bruin run` - it catches YAML errors, missing connections, broken refs, and type mismatches without burning compute\n- If validation fails, read the error, fix the root cause at the source, then re-validate. Do not chain `run` attempts hoping they work\n- After running, do **spot checks across layers**: row counts, date min/max, null counts, and a handful of sample rows at each tier (raw → staging → marts). A pipeline that \"succeeded\" can still have silently wrong data\n\n### Document as you add\n- Every asset: **top-level description** explaining what it produces and why\n- Every meaningful column: **column-level description**. Don't let `cost_micros`, `status_code`, or `price_adj` go undocumented\n- Add **tags** to group assets (e.g. `tier:raw`, `domain:marketing`, `owner:analyst`)\n- Add **quality checks** (`not_null`, `unique`, `accepted_values`, `positive`) on columns where the invariant matters\n- Add **custom checks** (SQL-backed assertions) for business rules that can't be expressed at the column level\n- Add **metadata** (owner, source system, refresh cadence, SLA) so future-you and future-agents have context\n\n### Keep this file current\n- When you learn a non-obvious fact about the data, a convention the user prefers, or a mistake worth avoiding - **append it to this file**. That's how the agent gets smarter across sessions\n\n### Naming and structure\n- Schemas: `raw.*` for ingestion output, `staging.*` for cleaned/typed, `marts.*` for business-ready tables\n- Asset file names should match the table they produce (e.g. `raw.google_ads_campaigns` → `google_ads_campaigns.asset.yml`)\n- Keep SQL assets under `\u003Cpipeline>/assets/`, same folder as ingestr assets\n- SQL assets should be **idempotent** - re-running produces the same output\n\n### Safety\n- **Never commit `.bruin.yml`.** It holds secrets. It's git-ignored by default; verify before staging\n- **Never run `DROP`, `DELETE`, `TRUNCATE`, or `UPDATE`** on raw tables - they're the ingestion output and should be rebuilt via `bruin run`, not mutated\n- When I say \"reset\" or \"start over\", ask me exactly what to drop before dropping anything\n\n### Show your work\n- Before running a command that modifies files or data, show the plan and wait for approval\n- Print the SQL before executing it\n- When editing YAML, show the final file or a diff - not just \"done\"\n",[2984],{"type":43,"tag":310,"props":2985,"children":2986},{"__ignoreMap":7},[2987,2995,3004,3012,3019,3027,3035,3043,3051,3059,3066,3075,3084,3093,3101,3109,3118,3127,3136,3145,3153,3162,3171,3180,3189,3197,3206,3215,3224,3233,3242,3251,3260,3268,3277,3286,3294,3303,3312,3321,3330,3339,3347,3356,3365,3374,3383,3391,3400,3409,3418],{"type":43,"tag":412,"props":2988,"children":2989},{"class":698,"line":699},[2990],{"type":43,"tag":412,"props":2991,"children":2992},{},[2993],{"type":49,"value":2994},"# AGENTS.md\n",{"type":43,"tag":412,"props":2996,"children":2997},{"class":698,"line":721},[2998],{"type":43,"tag":412,"props":2999,"children":3001},{"emptyLinePlaceholder":3000},true,[3002],{"type":49,"value":3003},"\n",{"type":43,"tag":412,"props":3005,"children":3006},{"class":698,"line":19},[3007],{"type":43,"tag":412,"props":3008,"children":3009},{},[3010],{"type":49,"value":3011},"## How you (the AI agent) should work in this project\n",{"type":43,"tag":412,"props":3013,"children":3014},{"class":698,"line":752},[3015],{"type":43,"tag":412,"props":3016,"children":3017},{"emptyLinePlaceholder":3000},[3018],{"type":49,"value":3003},{"type":43,"tag":412,"props":3020,"children":3021},{"class":698,"line":770},[3022],{"type":43,"tag":412,"props":3023,"children":3024},{},[3025],{"type":49,"value":3026},"### Ground rules\n",{"type":43,"tag":412,"props":3028,"children":3029},{"class":698,"line":788},[3030],{"type":43,"tag":412,"props":3031,"children":3032},{},[3033],{"type":49,"value":3034},"- This is a Bruin project. Use the **Bruin MCP** tools when available - they're the fastest, most reliable path\n",{"type":43,"tag":412,"props":3036,"children":3037},{"class":698,"line":806},[3038],{"type":43,"tag":412,"props":3039,"children":3040},{},[3041],{"type":49,"value":3042},"- Use the **Bruin CLI** for all pipeline operations: `bruin init`, `bruin run`, `bruin validate`, `bruin query`, `bruin ai enhance`, `bruin connections test`\n",{"type":43,"tag":412,"props":3044,"children":3045},{"class":698,"line":824},[3046],{"type":43,"tag":412,"props":3047,"children":3048},{},[3049],{"type":49,"value":3050},"- Reference the **[Bruin docs](https://getbruin.com/docs)** when unsure about asset types, materializations, connection configs, or CLI flags. Don't guess - the docs are authoritative\n",{"type":43,"tag":412,"props":3052,"children":3053},{"class":698,"line":842},[3054],{"type":43,"tag":412,"props":3055,"children":3056},{},[3057],{"type":49,"value":3058},"- **Ask me when unclear.** If a request is ambiguous (time range, columns, grain, attribution model, which environment, etc.), ask before guessing. A clarifying question now beats a wrong answer later\n",{"type":43,"tag":412,"props":3060,"children":3061},{"class":698,"line":860},[3062],{"type":43,"tag":412,"props":3063,"children":3064},{"emptyLinePlaceholder":3000},[3065],{"type":49,"value":3003},{"type":43,"tag":412,"props":3067,"children":3069},{"class":698,"line":3068},11,[3070],{"type":43,"tag":412,"props":3071,"children":3072},{},[3073],{"type":49,"value":3074},"### Environments\n",{"type":43,"tag":412,"props":3076,"children":3078},{"class":698,"line":3077},12,[3079],{"type":43,"tag":412,"props":3080,"children":3081},{},[3082],{"type":49,"value":3083},"- Before adding or changing connections, confirm with me whether this project uses a single `default` environment or separate `dev` / `prod` environments\n",{"type":43,"tag":412,"props":3085,"children":3087},{"class":698,"line":3086},13,[3088],{"type":43,"tag":412,"props":3089,"children":3090},{},[3091],{"type":49,"value":3092},"- Never copy secrets between environments without asking\n",{"type":43,"tag":412,"props":3094,"children":3095},{"class":698,"line":11},[3096],{"type":43,"tag":412,"props":3097,"children":3098},{},[3099],{"type":49,"value":3100},"- Keep env-specific values (DB paths, API keys, schemas) scoped to the right environment block\n",{"type":43,"tag":412,"props":3102,"children":3104},{"class":698,"line":3103},15,[3105],{"type":43,"tag":412,"props":3106,"children":3107},{"emptyLinePlaceholder":3000},[3108],{"type":49,"value":3003},{"type":43,"tag":412,"props":3110,"children":3112},{"class":698,"line":3111},16,[3113],{"type":43,"tag":412,"props":3114,"children":3115},{},[3116],{"type":49,"value":3117},"### Cap data volume when testing\n",{"type":43,"tag":412,"props":3119,"children":3121},{"class":698,"line":3120},17,[3122],{"type":43,"tag":412,"props":3123,"children":3124},{},[3125],{"type":49,"value":3126},"- For exploratory queries, use `LIMIT 20` (or fewer) until you've confirmed the shape of the result\n",{"type":43,"tag":412,"props":3128,"children":3130},{"class":698,"line":3129},18,[3131],{"type":43,"tag":412,"props":3132,"children":3133},{},[3134],{"type":49,"value":3135},"- For ingestr assets during testing, use Bruin's **interval dates** (`interval_start`, `interval_end`) to cap the backfill window - never pull unbounded history on a first run\n",{"type":43,"tag":412,"props":3137,"children":3139},{"class":698,"line":3138},19,[3140],{"type":43,"tag":412,"props":3141,"children":3142},{},[3143],{"type":49,"value":3144},"- Prefer **narrow, explicit date windows** (e.g. last 7 days) over open-ended scans\n",{"type":43,"tag":412,"props":3146,"children":3148},{"class":698,"line":3147},20,[3149],{"type":43,"tag":412,"props":3150,"children":3151},{"emptyLinePlaceholder":3000},[3152],{"type":49,"value":3003},{"type":43,"tag":412,"props":3154,"children":3156},{"class":698,"line":3155},21,[3157],{"type":43,"tag":412,"props":3158,"children":3159},{},[3160],{"type":49,"value":3161},"### Validate before you run\n",{"type":43,"tag":412,"props":3163,"children":3165},{"class":698,"line":3164},22,[3166],{"type":43,"tag":412,"props":3167,"children":3168},{},[3169],{"type":49,"value":3170},"- Run `bruin validate \u003Cpath>` before `bruin run` - it catches YAML errors, missing connections, broken refs, and type mismatches without burning compute\n",{"type":43,"tag":412,"props":3172,"children":3174},{"class":698,"line":3173},23,[3175],{"type":43,"tag":412,"props":3176,"children":3177},{},[3178],{"type":49,"value":3179},"- If validation fails, read the error, fix the root cause at the source, then re-validate. Do not chain `run` attempts hoping they work\n",{"type":43,"tag":412,"props":3181,"children":3183},{"class":698,"line":3182},24,[3184],{"type":43,"tag":412,"props":3185,"children":3186},{},[3187],{"type":49,"value":3188},"- After running, do **spot checks across layers**: row counts, date min/max, null counts, and a handful of sample rows at each tier (raw → staging → marts). A pipeline that \"succeeded\" can still have silently wrong data\n",{"type":43,"tag":412,"props":3190,"children":3192},{"class":698,"line":3191},25,[3193],{"type":43,"tag":412,"props":3194,"children":3195},{"emptyLinePlaceholder":3000},[3196],{"type":49,"value":3003},{"type":43,"tag":412,"props":3198,"children":3200},{"class":698,"line":3199},26,[3201],{"type":43,"tag":412,"props":3202,"children":3203},{},[3204],{"type":49,"value":3205},"### Document as you add\n",{"type":43,"tag":412,"props":3207,"children":3209},{"class":698,"line":3208},27,[3210],{"type":43,"tag":412,"props":3211,"children":3212},{},[3213],{"type":49,"value":3214},"- Every asset: **top-level description** explaining what it produces and why\n",{"type":43,"tag":412,"props":3216,"children":3218},{"class":698,"line":3217},28,[3219],{"type":43,"tag":412,"props":3220,"children":3221},{},[3222],{"type":49,"value":3223},"- Every meaningful column: **column-level description**. Don't let `cost_micros`, `status_code`, or `price_adj` go undocumented\n",{"type":43,"tag":412,"props":3225,"children":3227},{"class":698,"line":3226},29,[3228],{"type":43,"tag":412,"props":3229,"children":3230},{},[3231],{"type":49,"value":3232},"- Add **tags** to group assets (e.g. `tier:raw`, `domain:marketing`, `owner:analyst`)\n",{"type":43,"tag":412,"props":3234,"children":3236},{"class":698,"line":3235},30,[3237],{"type":43,"tag":412,"props":3238,"children":3239},{},[3240],{"type":49,"value":3241},"- Add **quality checks** (`not_null`, `unique`, `accepted_values`, `positive`) on columns where the invariant matters\n",{"type":43,"tag":412,"props":3243,"children":3245},{"class":698,"line":3244},31,[3246],{"type":43,"tag":412,"props":3247,"children":3248},{},[3249],{"type":49,"value":3250},"- Add **custom checks** (SQL-backed assertions) for business rules that can't be expressed at the column level\n",{"type":43,"tag":412,"props":3252,"children":3254},{"class":698,"line":3253},32,[3255],{"type":43,"tag":412,"props":3256,"children":3257},{},[3258],{"type":49,"value":3259},"- Add **metadata** (owner, source system, refresh cadence, SLA) so future-you and future-agents have context\n",{"type":43,"tag":412,"props":3261,"children":3263},{"class":698,"line":3262},33,[3264],{"type":43,"tag":412,"props":3265,"children":3266},{"emptyLinePlaceholder":3000},[3267],{"type":49,"value":3003},{"type":43,"tag":412,"props":3269,"children":3271},{"class":698,"line":3270},34,[3272],{"type":43,"tag":412,"props":3273,"children":3274},{},[3275],{"type":49,"value":3276},"### Keep this file current\n",{"type":43,"tag":412,"props":3278,"children":3280},{"class":698,"line":3279},35,[3281],{"type":43,"tag":412,"props":3282,"children":3283},{},[3284],{"type":49,"value":3285},"- When you learn a non-obvious fact about the data, a convention the user prefers, or a mistake worth avoiding - **append it to this file**. That's how the agent gets smarter across sessions\n",{"type":43,"tag":412,"props":3287,"children":3289},{"class":698,"line":3288},36,[3290],{"type":43,"tag":412,"props":3291,"children":3292},{"emptyLinePlaceholder":3000},[3293],{"type":49,"value":3003},{"type":43,"tag":412,"props":3295,"children":3297},{"class":698,"line":3296},37,[3298],{"type":43,"tag":412,"props":3299,"children":3300},{},[3301],{"type":49,"value":3302},"### Naming and structure\n",{"type":43,"tag":412,"props":3304,"children":3306},{"class":698,"line":3305},38,[3307],{"type":43,"tag":412,"props":3308,"children":3309},{},[3310],{"type":49,"value":3311},"- Schemas: `raw.*` for ingestion output, `staging.*` for cleaned/typed, `marts.*` for business-ready tables\n",{"type":43,"tag":412,"props":3313,"children":3315},{"class":698,"line":3314},39,[3316],{"type":43,"tag":412,"props":3317,"children":3318},{},[3319],{"type":49,"value":3320},"- Asset file names should match the table they produce (e.g. `raw.google_ads_campaigns` → `google_ads_campaigns.asset.yml`)\n",{"type":43,"tag":412,"props":3322,"children":3324},{"class":698,"line":3323},40,[3325],{"type":43,"tag":412,"props":3326,"children":3327},{},[3328],{"type":49,"value":3329},"- Keep SQL assets under `\u003Cpipeline>/assets/`, same folder as ingestr assets\n",{"type":43,"tag":412,"props":3331,"children":3333},{"class":698,"line":3332},41,[3334],{"type":43,"tag":412,"props":3335,"children":3336},{},[3337],{"type":49,"value":3338},"- SQL assets should be **idempotent** - re-running produces the same output\n",{"type":43,"tag":412,"props":3340,"children":3342},{"class":698,"line":3341},42,[3343],{"type":43,"tag":412,"props":3344,"children":3345},{"emptyLinePlaceholder":3000},[3346],{"type":49,"value":3003},{"type":43,"tag":412,"props":3348,"children":3350},{"class":698,"line":3349},43,[3351],{"type":43,"tag":412,"props":3352,"children":3353},{},[3354],{"type":49,"value":3355},"### Safety\n",{"type":43,"tag":412,"props":3357,"children":3359},{"class":698,"line":3358},44,[3360],{"type":43,"tag":412,"props":3361,"children":3362},{},[3363],{"type":49,"value":3364},"- **Never commit `.bruin.yml`.** It holds secrets. It's git-ignored by default; verify before staging\n",{"type":43,"tag":412,"props":3366,"children":3368},{"class":698,"line":3367},45,[3369],{"type":43,"tag":412,"props":3370,"children":3371},{},[3372],{"type":49,"value":3373},"- **Never run `DROP`, `DELETE`, `TRUNCATE`, or `UPDATE`** on raw tables - they're the ingestion output and should be rebuilt via `bruin run`, not mutated\n",{"type":43,"tag":412,"props":3375,"children":3377},{"class":698,"line":3376},46,[3378],{"type":43,"tag":412,"props":3379,"children":3380},{},[3381],{"type":49,"value":3382},"- When I say \"reset\" or \"start over\", ask me exactly what to drop before dropping anything\n",{"type":43,"tag":412,"props":3384,"children":3386},{"class":698,"line":3385},47,[3387],{"type":43,"tag":412,"props":3388,"children":3389},{"emptyLinePlaceholder":3000},[3390],{"type":49,"value":3003},{"type":43,"tag":412,"props":3392,"children":3394},{"class":698,"line":3393},48,[3395],{"type":43,"tag":412,"props":3396,"children":3397},{},[3398],{"type":49,"value":3399},"### Show your work\n",{"type":43,"tag":412,"props":3401,"children":3403},{"class":698,"line":3402},49,[3404],{"type":43,"tag":412,"props":3405,"children":3406},{},[3407],{"type":49,"value":3408},"- Before running a command that modifies files or data, show the plan and wait for approval\n",{"type":43,"tag":412,"props":3410,"children":3412},{"class":698,"line":3411},50,[3413],{"type":43,"tag":412,"props":3414,"children":3415},{},[3416],{"type":49,"value":3417},"- Print the SQL before executing it\n",{"type":43,"tag":412,"props":3419,"children":3421},{"class":698,"line":3420},51,[3422],{"type":43,"tag":412,"props":3423,"children":3424},{},[3425],{"type":49,"value":3426},"- When editing YAML, show the final file or a diff - not just \"done\"\n",{"type":43,"tag":52,"props":3428,"children":3429},{},[3430,3432,3437,3439,3444,3446,3452],{"type":49,"value":3431},"When the agent's done, you have two foundations in place: the project structure itself, and a root ",{"type":43,"tag":310,"props":3433,"children":3435},{"className":3434},[],[3436],{"type":49,"value":2562},{"type":49,"value":3438}," that will travel with the workspace and keep every future session consistent. In Step 4 you'll add a second, pipeline-specific ",{"type":43,"tag":310,"props":3440,"children":3442},{"className":3441},[],[3443],{"type":49,"value":2562},{"type":49,"value":3445}," inside the ",{"type":43,"tag":310,"props":3447,"children":3449},{"className":3448},[],[3450],{"type":49,"value":3451},"marketing-analyst-101/",{"type":49,"value":3453}," folder for the marketing-specific domain knowledge - keeping general Bruin rules at the workspace level and domain context scoped to the pipeline.",{"type":43,"tag":44,"props":3455,"children":3456},{"id":1070},[3457],{"type":49,"value":1073},{"type":43,"tag":52,"props":3459,"children":3460},{},[3461,3463,3468],{"type":49,"value":3462},"You have a Bruin project with a working DuckDB connection and an ",{"type":43,"tag":310,"props":3464,"children":3466},{"className":3465},[],[3467],{"type":49,"value":2562},{"type":49,"value":3469}," that tells the AI agent exactly how to behave inside this project. No data in it yet, but the plumbing and the playbook are both in place. Next step: plug in Google Ads, Klaviyo, and GA4 as data sources and let Bruin pull real data into DuckDB.",{"type":43,"tag":1087,"props":3471,"children":3472},{},[3473],{"type":49,"value":1091},{"title":7,"searchDepth":721,"depth":721,"links":3475},[3476,3477,3478,3479,3480,3482,3484],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":2632,"depth":721,"text":2635},{"id":2729,"depth":721,"text":2732},{"id":2771,"depth":721,"text":3481},"Peek inside .bruin.yml",{"id":2892,"depth":721,"text":3483},"Seed AGENTS.md with Bruin rules",{"id":1070,"depth":721,"text":1073},"content:tutorials:marketing-analyst-101:scaffold-template.md","tutorials/marketing-analyst-101/scaffold-template.md","tutorials/marketing-analyst-101/scaffold-template",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"readingTime":11,"category":12,"tags":3489,"difficulty":18,"module":5,"step":19,"journeys":3490,"roles":3491,"learnMore":3492,"author":3496,"body":3497,"_type":1103,"_id":1104,"_source":1105,"_file":1106,"_stem":1107,"_extension":1108},[14,15,16,17],[21],[23,24],[3493,3494,3495],{"label":27,"url":28},{"label":30,"url":31},{"label":33,"url":34},{"name":36,"role":37,"image":38},{"type":40,"children":3498,"toc":4331},[3499,3503,3507,3511,3515,3542,3546,3550,3684,3688,3692,3785,3831,3841,3870,3874,3878,4003,4007,4159,4173,4177,4181,4194,4210,4214,4218,4258,4262,4266,4313,4317,4327],{"type":43,"tag":44,"props":3500,"children":3501},{"id":46},[3502],{"type":49,"value":50},{"type":43,"tag":52,"props":3504,"children":3505},{},[3506],{"type":49,"value":56},{"type":43,"tag":44,"props":3508,"children":3509},{"id":59},[3510],{"type":49,"value":62},{"type":43,"tag":52,"props":3512,"children":3513},{},[3514],{"type":49,"value":67},{"type":43,"tag":69,"props":3516,"children":3517},{},[3518,3526,3534],{"type":43,"tag":73,"props":3519,"children":3520},{},[3521,3525],{"type":43,"tag":77,"props":3522,"children":3523},{},[3524],{"type":49,"value":81},{"type":49,"value":83},{"type":43,"tag":73,"props":3527,"children":3528},{},[3529,3533],{"type":43,"tag":77,"props":3530,"children":3531},{},[3532],{"type":49,"value":91},{"type":49,"value":93},{"type":43,"tag":73,"props":3535,"children":3536},{},[3537,3541],{"type":43,"tag":77,"props":3538,"children":3539},{},[3540],{"type":49,"value":101},{"type":49,"value":103},{"type":43,"tag":52,"props":3543,"children":3544},{},[3545],{"type":49,"value":108},{"type":43,"tag":44,"props":3547,"children":3548},{"id":111},[3549],{"type":49,"value":114},{"type":43,"tag":116,"props":3551,"children":3552},{":variants":118},[3553,3619,3645],{"type":43,"tag":121,"props":3554,"children":3555},{"v-slot:google-ads":7},[3556,3560,3564,3603],{"type":43,"tag":125,"props":3557,"children":3558},{"id":127},[3559],{"type":49,"value":81},{"type":43,"tag":52,"props":3561,"children":3562},{},[3563],{"type":49,"value":134},{"type":43,"tag":69,"props":3565,"children":3566},{},[3567,3575,3583],{"type":43,"tag":73,"props":3568,"children":3569},{},[3570,3574],{"type":43,"tag":77,"props":3571,"children":3572},{},[3573],{"type":49,"value":145},{"type":49,"value":147},{"type":43,"tag":73,"props":3576,"children":3577},{},[3578,3582],{"type":43,"tag":77,"props":3579,"children":3580},{},[3581],{"type":49,"value":155},{"type":49,"value":157},{"type":43,"tag":73,"props":3584,"children":3585},{},[3586,3590,3591,3596,3597,3602],{"type":43,"tag":77,"props":3587,"children":3588},{},[3589],{"type":49,"value":165},{"type":49,"value":167},{"type":43,"tag":169,"props":3592,"children":3594},{"href":171,"rel":3593},[173],[3595],{"type":49,"value":176},{"type":49,"value":178},{"type":43,"tag":169,"props":3598,"children":3600},{"href":181,"rel":3599},[173],[3601],{"type":49,"value":185},{"type":49,"value":187},{"type":43,"tag":189,"props":3604,"children":3605},{},[3606],{"type":43,"tag":52,"props":3607,"children":3608},{},[3609,3613,3614,3618],{"type":43,"tag":77,"props":3610,"children":3611},{},[3612],{"type":49,"value":199},{"type":49,"value":201},{"type":43,"tag":203,"props":3615,"children":3616},{},[3617],{"type":49,"value":207},{"type":49,"value":209},{"type":43,"tag":121,"props":3620,"children":3621},{"v-slot:klaviyo":7},[3622,3626,3630,3641],{"type":43,"tag":125,"props":3623,"children":3624},{"id":215},[3625],{"type":49,"value":91},{"type":43,"tag":52,"props":3627,"children":3628},{},[3629],{"type":49,"value":222},{"type":43,"tag":69,"props":3631,"children":3632},{},[3633],{"type":43,"tag":73,"props":3634,"children":3635},{},[3636,3640],{"type":43,"tag":77,"props":3637,"children":3638},{},[3639],{"type":49,"value":233},{"type":49,"value":235},{"type":43,"tag":52,"props":3642,"children":3643},{},[3644],{"type":49,"value":240},{"type":43,"tag":121,"props":3646,"children":3647},{"v-slot:ga4":7},[3648,3652,3656],{"type":43,"tag":125,"props":3649,"children":3650},{"id":246},[3651],{"type":49,"value":101},{"type":43,"tag":52,"props":3653,"children":3654},{},[3655],{"type":49,"value":253},{"type":43,"tag":69,"props":3657,"children":3658},{},[3659,3667,3675],{"type":43,"tag":73,"props":3660,"children":3661},{},[3662,3666],{"type":43,"tag":77,"props":3663,"children":3664},{},[3665],{"type":49,"value":264},{"type":49,"value":266},{"type":43,"tag":73,"props":3668,"children":3669},{},[3670,3674],{"type":43,"tag":77,"props":3671,"children":3672},{},[3673],{"type":49,"value":274},{"type":49,"value":276},{"type":43,"tag":73,"props":3676,"children":3677},{},[3678,3679,3683],{"type":49,"value":281},{"type":43,"tag":77,"props":3680,"children":3681},{},[3682],{"type":49,"value":286},{"type":49,"value":288},{"type":43,"tag":44,"props":3685,"children":3686},{"id":291},[3687],{"type":49,"value":294},{"type":43,"tag":52,"props":3689,"children":3690},{},[3691],{"type":49,"value":299},{"type":43,"tag":301,"props":3693,"children":3694},{},[3695,3711,3762,3778],{"type":43,"tag":52,"props":3696,"children":3697},{},[3698,3699,3704,3705,3710],{"type":49,"value":308},{"type":43,"tag":310,"props":3700,"children":3702},{"className":3701},[],[3703],{"type":49,"value":315},{"type":49,"value":317},{"type":43,"tag":310,"props":3706,"children":3708},{"className":3707},[],[3709],{"type":49,"value":323},{"type":49,"value":325},{"type":43,"tag":327,"props":3712,"children":3713},{},[3714,3730,3746],{"type":43,"tag":73,"props":3715,"children":3716},{},[3717,3718,3723,3724,3729],{"type":49,"value":334},{"type":43,"tag":310,"props":3719,"children":3721},{"className":3720},[],[3722],{"type":49,"value":340},{"type":49,"value":342},{"type":43,"tag":310,"props":3725,"children":3727},{"className":3726},[],[3728],{"type":49,"value":348},{"type":49,"value":350},{"type":43,"tag":73,"props":3731,"children":3732},{},[3733,3734,3739,3740,3745],{"type":49,"value":334},{"type":43,"tag":310,"props":3735,"children":3737},{"className":3736},[],[3738],{"type":49,"value":215},{"type":49,"value":342},{"type":43,"tag":310,"props":3741,"children":3743},{"className":3742},[],[3744],{"type":49,"value":366},{"type":49,"value":368},{"type":43,"tag":73,"props":3747,"children":3748},{},[3749,3750,3755,3756,3761],{"type":49,"value":334},{"type":43,"tag":310,"props":3751,"children":3753},{"className":3752},[],[3754],{"type":49,"value":378},{"type":49,"value":342},{"type":43,"tag":310,"props":3757,"children":3759},{"className":3758},[],[3760],{"type":49,"value":385},{"type":49,"value":387},{"type":43,"tag":52,"props":3763,"children":3764},{},[3765,3766,3771,3772,3777],{"type":49,"value":392},{"type":43,"tag":169,"props":3767,"children":3769},{"href":28,"rel":3768},[173],[3770],{"type":49,"value":28},{"type":49,"value":399},{"type":43,"tag":310,"props":3773,"children":3775},{"className":3774},[],[3776],{"type":49,"value":405},{"type":49,"value":407},{"type":43,"tag":52,"props":3779,"children":3780},{},[3781],{"type":43,"tag":412,"props":3782,"children":3783},{},[3784],{"type":49,"value":416},{"type":43,"tag":189,"props":3786,"children":3787},{},[3788],{"type":43,"tag":52,"props":3789,"children":3790},{},[3791,3795,3796,3801,3802,3806,3807,3812,3813,3818,3819,3824,3825,3830],{"type":43,"tag":77,"props":3792,"children":3793},{},[3794],{"type":49,"value":427},{"type":49,"value":429},{"type":43,"tag":310,"props":3797,"children":3799},{"className":3798},[],[3800],{"type":49,"value":315},{"type":49,"value":436},{"type":43,"tag":77,"props":3803,"children":3804},{},[3805],{"type":49,"value":441},{"type":49,"value":443},{"type":43,"tag":310,"props":3808,"children":3810},{"className":3809},[],[3811],{"type":49,"value":449},{"type":49,"value":451},{"type":43,"tag":310,"props":3814,"children":3816},{"className":3815},[],[3817],{"type":49,"value":457},{"type":49,"value":451},{"type":43,"tag":310,"props":3820,"children":3822},{"className":3821},[],[3823],{"type":49,"value":464},{"type":49,"value":466},{"type":43,"tag":310,"props":3826,"children":3828},{"className":3827},[],[3829],{"type":49,"value":315},{"type":49,"value":473},{"type":43,"tag":52,"props":3832,"children":3833},{},[3834,3835,3840],{"type":49,"value":478},{"type":43,"tag":310,"props":3836,"children":3838},{"className":3837},[],[3839],{"type":49,"value":315},{"type":49,"value":485},{"type":43,"tag":189,"props":3842,"children":3843},{},[3844],{"type":43,"tag":52,"props":3845,"children":3846},{},[3847,3851,3852,3857,3858,3863,3864,3869],{"type":43,"tag":77,"props":3848,"children":3849},{},[3850],{"type":49,"value":496},{"type":49,"value":498},{"type":43,"tag":310,"props":3853,"children":3855},{"className":3854},[],[3856],{"type":49,"value":315},{"type":49,"value":505},{"type":43,"tag":310,"props":3859,"children":3861},{"className":3860},[],[3862],{"type":49,"value":511},{"type":49,"value":513},{"type":43,"tag":169,"props":3865,"children":3867},{"href":516,"rel":3866},[173],[3868],{"type":49,"value":520},{"type":49,"value":522},{"type":43,"tag":44,"props":3871,"children":3872},{"id":525},[3873],{"type":49,"value":528},{"type":43,"tag":52,"props":3875,"children":3876},{},[3877],{"type":49,"value":533},{"type":43,"tag":301,"props":3879,"children":3880},{},[3881,3891,3927,3963,3999],{"type":43,"tag":52,"props":3882,"children":3883},{},[3884,3885,3890],{"type":49,"value":541},{"type":43,"tag":310,"props":3886,"children":3888},{"className":3887},[],[3889],{"type":49,"value":547},{"type":49,"value":549},{"type":43,"tag":52,"props":3892,"children":3893},{},[3894,3902,3903,3908,3909,3914,3915,3920,3921,3926],{"type":43,"tag":77,"props":3895,"children":3896},{},[3897],{"type":43,"tag":310,"props":3898,"children":3900},{"className":3899},[],[3901],{"type":49,"value":561},{"type":49,"value":563},{"type":43,"tag":310,"props":3904,"children":3906},{"className":3905},[],[3907],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":3910,"children":3912},{"className":3911},[],[3913],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":3916,"children":3918},{"className":3917},[],[3919],{"type":49,"value":585},{"type":49,"value":587},{"type":43,"tag":310,"props":3922,"children":3924},{"className":3923},[],[3925],{"type":49,"value":593},{"type":49,"value":595},{"type":43,"tag":52,"props":3928,"children":3929},{},[3930,3938,3939,3944,3945,3950,3951,3956,3957,3962],{"type":43,"tag":77,"props":3931,"children":3932},{},[3933],{"type":43,"tag":310,"props":3934,"children":3936},{"className":3935},[],[3937],{"type":49,"value":607},{"type":49,"value":609},{"type":43,"tag":310,"props":3940,"children":3942},{"className":3941},[],[3943],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":3946,"children":3948},{"className":3947},[],[3949],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":3952,"children":3954},{"className":3953},[],[3955],{"type":49,"value":627},{"type":49,"value":587},{"type":43,"tag":310,"props":3958,"children":3960},{"className":3959},[],[3961],{"type":49,"value":634},{"type":49,"value":595},{"type":43,"tag":52,"props":3964,"children":3965},{},[3966,3974,3975,3980,3981,3986,3987,3992,3993,3998],{"type":43,"tag":77,"props":3967,"children":3968},{},[3969],{"type":43,"tag":310,"props":3970,"children":3972},{"className":3971},[],[3973],{"type":49,"value":647},{"type":49,"value":649},{"type":43,"tag":310,"props":3976,"children":3978},{"className":3977},[],[3979],{"type":49,"value":569},{"type":49,"value":571},{"type":43,"tag":310,"props":3982,"children":3984},{"className":3983},[],[3985],{"type":49,"value":577},{"type":49,"value":579},{"type":43,"tag":310,"props":3988,"children":3990},{"className":3989},[],[3991],{"type":49,"value":667},{"type":49,"value":587},{"type":43,"tag":310,"props":3994,"children":3996},{"className":3995},[],[3997],{"type":49,"value":674},{"type":49,"value":595},{"type":43,"tag":52,"props":4000,"children":4001},{},[4002],{"type":49,"value":680},{"type":43,"tag":52,"props":4004,"children":4005},{},[4006],{"type":49,"value":685},{"type":43,"tag":687,"props":4008,"children":4009},{"className":689,"code":690,"language":691,"meta":7,"style":7},[4010],{"type":43,"tag":310,"props":4011,"children":4012},{"__ignoreMap":7},[4013,4028,4043,4054,4069,4084,4099,4114,4129,4144],{"type":43,"tag":412,"props":4014,"children":4015},{"class":698,"line":699},[4016,4020,4024],{"type":43,"tag":412,"props":4017,"children":4018},{"style":703},[4019],{"type":49,"value":706},{"type":43,"tag":412,"props":4021,"children":4022},{"style":709},[4023],{"type":49,"value":712},{"type":43,"tag":412,"props":4025,"children":4026},{"style":715},[4027],{"type":49,"value":718},{"type":43,"tag":412,"props":4029,"children":4030},{"class":698,"line":721},[4031,4035,4039],{"type":43,"tag":412,"props":4032,"children":4033},{"style":703},[4034],{"type":49,"value":727},{"type":43,"tag":412,"props":4036,"children":4037},{"style":709},[4038],{"type":49,"value":712},{"type":43,"tag":412,"props":4040,"children":4041},{"style":715},[4042],{"type":49,"value":736},{"type":43,"tag":412,"props":4044,"children":4045},{"class":698,"line":19},[4046,4050],{"type":43,"tag":412,"props":4047,"children":4048},{"style":703},[4049],{"type":49,"value":744},{"type":43,"tag":412,"props":4051,"children":4052},{"style":709},[4053],{"type":49,"value":749},{"type":43,"tag":412,"props":4055,"children":4056},{"class":698,"line":752},[4057,4061,4065],{"type":43,"tag":412,"props":4058,"children":4059},{"style":703},[4060],{"type":49,"value":758},{"type":43,"tag":412,"props":4062,"children":4063},{"style":709},[4064],{"type":49,"value":712},{"type":43,"tag":412,"props":4066,"children":4067},{"style":715},[4068],{"type":49,"value":767},{"type":43,"tag":412,"props":4070,"children":4071},{"class":698,"line":770},[4072,4076,4080],{"type":43,"tag":412,"props":4073,"children":4074},{"style":703},[4075],{"type":49,"value":776},{"type":43,"tag":412,"props":4077,"children":4078},{"style":709},[4079],{"type":49,"value":712},{"type":43,"tag":412,"props":4081,"children":4082},{"style":715},[4083],{"type":49,"value":785},{"type":43,"tag":412,"props":4085,"children":4086},{"class":698,"line":788},[4087,4091,4095],{"type":43,"tag":412,"props":4088,"children":4089},{"style":703},[4090],{"type":49,"value":794},{"type":43,"tag":412,"props":4092,"children":4093},{"style":709},[4094],{"type":49,"value":712},{"type":43,"tag":412,"props":4096,"children":4097},{"style":715},[4098],{"type":49,"value":803},{"type":43,"tag":412,"props":4100,"children":4101},{"class":698,"line":806},[4102,4106,4110],{"type":43,"tag":412,"props":4103,"children":4104},{"style":703},[4105],{"type":49,"value":812},{"type":43,"tag":412,"props":4107,"children":4108},{"style":709},[4109],{"type":49,"value":712},{"type":43,"tag":412,"props":4111,"children":4112},{"style":715},[4113],{"type":49,"value":821},{"type":43,"tag":412,"props":4115,"children":4116},{"class":698,"line":824},[4117,4121,4125],{"type":43,"tag":412,"props":4118,"children":4119},{"style":703},[4120],{"type":49,"value":830},{"type":43,"tag":412,"props":4122,"children":4123},{"style":709},[4124],{"type":49,"value":712},{"type":43,"tag":412,"props":4126,"children":4127},{"style":715},[4128],{"type":49,"value":839},{"type":43,"tag":412,"props":4130,"children":4131},{"class":698,"line":842},[4132,4136,4140],{"type":43,"tag":412,"props":4133,"children":4134},{"style":703},[4135],{"type":49,"value":848},{"type":43,"tag":412,"props":4137,"children":4138},{"style":709},[4139],{"type":49,"value":712},{"type":43,"tag":412,"props":4141,"children":4142},{"style":715},[4143],{"type":49,"value":857},{"type":43,"tag":412,"props":4145,"children":4146},{"class":698,"line":860},[4147,4151,4155],{"type":43,"tag":412,"props":4148,"children":4149},{"style":703},[4150],{"type":49,"value":866},{"type":43,"tag":412,"props":4152,"children":4153},{"style":709},[4154],{"type":49,"value":712},{"type":43,"tag":412,"props":4156,"children":4157},{"style":715},[4158],{"type":49,"value":875},{"type":43,"tag":52,"props":4160,"children":4161},{},[4162,4163,4167,4168,4172],{"type":49,"value":880},{"type":43,"tag":169,"props":4164,"children":4165},{"href":34},[4166],{"type":49,"value":885},{"type":49,"value":887},{"type":43,"tag":77,"props":4169,"children":4170},{},[4171],{"type":49,"value":892},{"type":49,"value":894},{"type":43,"tag":44,"props":4174,"children":4175},{"id":897},[4176],{"type":49,"value":900},{"type":43,"tag":52,"props":4178,"children":4179},{},[4180],{"type":49,"value":905},{"type":43,"tag":301,"props":4182,"children":4183},{},[4184],{"type":43,"tag":52,"props":4185,"children":4186},{},[4187,4188,4193],{"type":49,"value":913},{"type":43,"tag":310,"props":4189,"children":4191},{"className":4190},[],[4192],{"type":49,"value":919},{"type":49,"value":921},{"type":43,"tag":52,"props":4195,"children":4196},{},[4197,4198,4203,4204,4209],{"type":49,"value":926},{"type":43,"tag":310,"props":4199,"children":4201},{"className":4200},[],[4202],{"type":49,"value":932},{"type":49,"value":934},{"type":43,"tag":310,"props":4205,"children":4207},{"className":4206},[],[4208],{"type":49,"value":940},{"type":49,"value":595},{"type":43,"tag":44,"props":4211,"children":4212},{"id":944},[4213],{"type":49,"value":947},{"type":43,"tag":52,"props":4215,"children":4216},{},[4217],{"type":49,"value":952},{"type":43,"tag":301,"props":4219,"children":4220},{},[4221,4231],{"type":43,"tag":52,"props":4222,"children":4223},{},[4224,4225,4230],{"type":49,"value":960},{"type":43,"tag":310,"props":4226,"children":4228},{"className":4227},[],[4229],{"type":49,"value":966},{"type":49,"value":968},{"type":43,"tag":327,"props":4232,"children":4233},{},[4234,4242,4250],{"type":43,"tag":73,"props":4235,"children":4236},{},[4237],{"type":43,"tag":310,"props":4238,"children":4240},{"className":4239},[],[4241],{"type":49,"value":980},{"type":43,"tag":73,"props":4243,"children":4244},{},[4245],{"type":43,"tag":310,"props":4246,"children":4248},{"className":4247},[],[4249],{"type":49,"value":989},{"type":43,"tag":73,"props":4251,"children":4252},{},[4253],{"type":43,"tag":310,"props":4254,"children":4256},{"className":4255},[],[4257],{"type":49,"value":998},{"type":43,"tag":52,"props":4259,"children":4260},{},[4261],{"type":49,"value":1003},{"type":43,"tag":44,"props":4263,"children":4264},{"id":1006},[4265],{"type":49,"value":1009},{"type":43,"tag":69,"props":4267,"children":4268},{},[4269,4289,4297,4305],{"type":43,"tag":73,"props":4270,"children":4271},{},[4272,4276,4277,4282,4283,4288],{"type":43,"tag":77,"props":4273,"children":4274},{},[4275],{"type":49,"value":1020},{"type":49,"value":1022},{"type":43,"tag":310,"props":4278,"children":4280},{"className":4279},[],[4281],{"type":49,"value":1028},{"type":49,"value":1030},{"type":43,"tag":310,"props":4284,"children":4286},{"className":4285},[],[4287],{"type":49,"value":1036},{"type":49,"value":595},{"type":43,"tag":73,"props":4290,"children":4291},{},[4292,4296],{"type":43,"tag":77,"props":4293,"children":4294},{},[4295],{"type":49,"value":1045},{"type":49,"value":1047},{"type":43,"tag":73,"props":4298,"children":4299},{},[4300,4304],{"type":43,"tag":77,"props":4301,"children":4302},{},[4303],{"type":49,"value":1055},{"type":49,"value":1057},{"type":43,"tag":73,"props":4306,"children":4307},{},[4308,4312],{"type":43,"tag":77,"props":4309,"children":4310},{},[4311],{"type":49,"value":1065},{"type":49,"value":1067},{"type":43,"tag":44,"props":4314,"children":4315},{"id":1070},[4316],{"type":49,"value":1073},{"type":43,"tag":52,"props":4318,"children":4319},{},[4320,4321,4326],{"type":49,"value":1078},{"type":43,"tag":310,"props":4322,"children":4324},{"className":4323},[],[4325],{"type":49,"value":940},{"type":49,"value":1085},{"type":43,"tag":1087,"props":4328,"children":4329},{},[4330],{"type":49,"value":1091},{"title":7,"searchDepth":721,"depth":721,"links":4332},[4333,4334,4335,4336,4337,4338,4339,4340,4341],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":111,"depth":721,"text":114},{"id":291,"depth":721,"text":294},{"id":525,"depth":721,"text":528},{"id":897,"depth":721,"text":900},{"id":944,"depth":721,"text":947},{"id":1006,"depth":721,"text":1009},{"id":1070,"depth":721,"text":1073},{"_path":4343,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":4344,"description":4345,"date":10,"readingTime":3077,"category":12,"tags":4346,"difficulty":18,"module":5,"step":752,"journeys":4348,"roles":4349,"learnMore":4350,"author":4360,"body":4361,"_type":1103,"_id":5374,"_source":1105,"_file":5375,"_stem":5376,"_extension":1108},"/tutorials/marketing-analyst-101/transform-reports","Build Staging + Report Layers","Add a clean middle layer on top of the raw ad, email, and web data, then build a final report table that unifies channel-level performance - the analysis-ready table your AI agent will query.",[14,15,16,17,4347],"SQL",[21],[23,24],[4351,4354,4357],{"label":4352,"url":4353},"Bruin SQL asset type","https://getbruin.com/docs/bruin/assets/sql.html",{"label":4355,"url":4356},"Bruin materializations","https://getbruin.com/docs/bruin/assets/materialization.html",{"label":4358,"url":4359},"Bruin quality checks","https://getbruin.com/docs/bruin/quality-checks/overview.html",{"name":36,"role":37,"image":38},{"type":40,"children":4362,"toc":5365},[4363,4367,4372,4407,4411,4424,4429,4471,4490,4496,4500,4900,4918,4924,4928,5167,5172,5178,5182,5284,5296,5302,5307,5316,5338,5356,5360],{"type":43,"tag":44,"props":4364,"children":4365},{"id":46},[4366],{"type":49,"value":50},{"type":43,"tag":52,"props":4368,"children":4369},{},[4370],{"type":49,"value":4371},"Ask the agent to add two new layers of SQL assets on top of the raw tables from Step 3:",{"type":43,"tag":327,"props":4373,"children":4374},{},[4375,4391],{"type":43,"tag":73,"props":4376,"children":4377},{},[4378,4389],{"type":43,"tag":77,"props":4379,"children":4380},{},[4381,4387],{"type":43,"tag":310,"props":4382,"children":4384},{"className":4383},[],[4385],{"type":49,"value":4386},"staging.*",{"type":49,"value":4388}," views",{"type":49,"value":4390}," - one per raw source, cleaning and typing the data, converting units (micros → USD), computing derived columns",{"type":43,"tag":73,"props":4392,"children":4393},{},[4394,4405],{"type":43,"tag":77,"props":4395,"children":4396},{},[4397,4403],{"type":43,"tag":310,"props":4398,"children":4400},{"className":4399},[],[4401],{"type":49,"value":4402},"reports.*",{"type":49,"value":4404}," table",{"type":49,"value":4406}," - the cross-channel analysis-ready surface that the AI will query in Step 6",{"type":43,"tag":44,"props":4408,"children":4409},{"id":59},[4410],{"type":49,"value":62},{"type":43,"tag":52,"props":4412,"children":4413},{},[4414,4416,4422],{"type":49,"value":4415},"Your raw tables are the source of truth - but they're messy: ",{"type":43,"tag":310,"props":4417,"children":4419},{"className":4418},[],[4420],{"type":49,"value":4421},"cost_micros",{"type":49,"value":4423}," is in millionths of a dollar, GA4 has 24–48 hour reporting delays, Klaviyo opens are inflated by Apple Mail Privacy Protection, and each source reports in its own attribution model. Asking analytical questions directly against raw means re-deriving the same transforms every time, in every prompt.",{"type":43,"tag":52,"props":4425,"children":4426},{},[4427],{"type":49,"value":4428},"The layered pattern fixes that once:",{"type":43,"tag":69,"props":4430,"children":4431},{},[4432,4445,4458],{"type":43,"tag":73,"props":4433,"children":4434},{},[4435,4443],{"type":43,"tag":77,"props":4436,"children":4437},{},[4438],{"type":43,"tag":310,"props":4439,"children":4441},{"className":4440},[],[4442],{"type":49,"value":932},{"type":49,"value":4444}," - untouched copy of what each source returned (Step 3 output)",{"type":43,"tag":73,"props":4446,"children":4447},{},[4448,4456],{"type":43,"tag":77,"props":4449,"children":4450},{},[4451],{"type":43,"tag":310,"props":4452,"children":4454},{"className":4453},[],[4455],{"type":49,"value":4386},{"type":49,"value":4457}," - cleaned, typed, deduplicated, unit-normalized. One view per source.",{"type":43,"tag":73,"props":4459,"children":4460},{},[4461,4469],{"type":43,"tag":77,"props":4462,"children":4463},{},[4464],{"type":43,"tag":310,"props":4465,"children":4467},{"className":4466},[],[4468],{"type":49,"value":4402},{"type":49,"value":4470}," - business-ready cross-channel joins with pre-computed KPIs (CAC, ROAS, CTR, CVR, blended CAC by day)",{"type":43,"tag":52,"props":4472,"children":4473},{},[4474,4476,4481,4483,4488],{"type":49,"value":4475},"Once this layer exists, you ask questions against ",{"type":43,"tag":310,"props":4477,"children":4479},{"className":4478},[],[4480],{"type":49,"value":4402},{"type":49,"value":4482}," and the AI doesn't have to remember to divide ",{"type":43,"tag":310,"props":4484,"children":4486},{"className":4485},[],[4487],{"type":49,"value":4421},{"type":49,"value":4489}," by 1,000,000 or pick an attribution model every session. Faster, cheaper, less room for mistakes.",{"type":43,"tag":44,"props":4491,"children":4493},{"id":4492},"_1-scaffold-the-staging-views",[4494],{"type":49,"value":4495},"1. Scaffold the staging views",{"type":43,"tag":52,"props":4497,"children":4498},{},[4499],{"type":49,"value":299},{"type":43,"tag":301,"props":4501,"children":4502},{},[4503,4528,4548,4630,4649,4749,4768,4866,4895],{"type":43,"tag":52,"props":4504,"children":4505},{},[4506,4507,4512,4514,4519,4521,4527],{"type":49,"value":541},{"type":43,"tag":310,"props":4508,"children":4510},{"className":4509},[],[4511],{"type":49,"value":547},{"type":49,"value":4513},", create three Bruin SQL assets for the staging layer. All should materialize as ",{"type":43,"tag":77,"props":4515,"children":4516},{},[4517],{"type":49,"value":4518},"views",{"type":49,"value":4520}," (cheap, always fresh) with schema ",{"type":43,"tag":310,"props":4522,"children":4524},{"className":4523},[],[4525],{"type":49,"value":4526},"staging",{"type":49,"value":595},{"type":43,"tag":52,"props":4529,"children":4530},{},[4531,4540,4542],{"type":43,"tag":77,"props":4532,"children":4533},{},[4534],{"type":43,"tag":310,"props":4535,"children":4537},{"className":4536},[],[4538],{"type":49,"value":4539},"staging/google_ads.sql",{"type":49,"value":4541}," → ",{"type":43,"tag":310,"props":4543,"children":4545},{"className":4544},[],[4546],{"type":49,"value":4547},"staging.google_ads",{"type":43,"tag":69,"props":4549,"children":4550},{},[4551,4562,4581,4600,4625],{"type":43,"tag":73,"props":4552,"children":4553},{},[4554,4556],{"type":49,"value":4555},"Source: ",{"type":43,"tag":310,"props":4557,"children":4559},{"className":4558},[],[4560],{"type":49,"value":4561},"raw.google_ads_campaigns",{"type":43,"tag":73,"props":4563,"children":4564},{},[4565,4567,4573,4575],{"type":49,"value":4566},"Cast ",{"type":43,"tag":310,"props":4568,"children":4570},{"className":4569},[],[4571],{"type":49,"value":4572},"date",{"type":49,"value":4574}," to DATE, trim/dedupe on (campaign_id, date) using ",{"type":43,"tag":310,"props":4576,"children":4578},{"className":4577},[],[4579],{"type":49,"value":4580},"QUALIFY ROW_NUMBER() OVER (PARTITION BY campaign_id, date ORDER BY date DESC) = 1",{"type":43,"tag":73,"props":4582,"children":4583},{},[4584,4586,4591,4592,4598],{"type":49,"value":4585},"Convert ",{"type":43,"tag":310,"props":4587,"children":4589},{"className":4588},[],[4590],{"type":49,"value":4421},{"type":49,"value":4541},{"type":43,"tag":310,"props":4593,"children":4595},{"className":4594},[],[4596],{"type":49,"value":4597},"cost_usd",{"type":49,"value":4599}," = cost_micros / 1e6",{"type":43,"tag":73,"props":4601,"children":4602},{},[4603,4605,4611,4612,4618,4619],{"type":49,"value":4604},"Derived metrics per row: ",{"type":43,"tag":310,"props":4606,"children":4608},{"className":4607},[],[4609],{"type":49,"value":4610},"ctr = clicks / NULLIF(impressions, 0)",{"type":49,"value":451},{"type":43,"tag":310,"props":4613,"children":4615},{"className":4614},[],[4616],{"type":49,"value":4617},"cvr = conversions / NULLIF(clicks, 0)",{"type":49,"value":451},{"type":43,"tag":310,"props":4620,"children":4622},{"className":4621},[],[4623],{"type":49,"value":4624},"cpc_usd = cost_usd / NULLIF(clicks, 0)",{"type":43,"tag":73,"props":4626,"children":4627},{},[4628],{"type":49,"value":4629},"Keep: campaign_id, campaign_name, date, impressions, clicks, conversions, conversion_value, cost_usd, ctr, cvr, cpc_usd",{"type":43,"tag":52,"props":4631,"children":4632},{},[4633,4642,4643],{"type":43,"tag":77,"props":4634,"children":4635},{},[4636],{"type":43,"tag":310,"props":4637,"children":4639},{"className":4638},[],[4640],{"type":49,"value":4641},"staging/klaviyo.sql",{"type":49,"value":4541},{"type":43,"tag":310,"props":4644,"children":4646},{"className":4645},[],[4647],{"type":49,"value":4648},"staging.klaviyo",{"type":43,"tag":69,"props":4650,"children":4651},{},[4652,4662,4680,4685,4710,4744],{"type":43,"tag":73,"props":4653,"children":4654},{},[4655,4656],{"type":49,"value":4555},{"type":43,"tag":310,"props":4657,"children":4659},{"className":4658},[],[4660],{"type":49,"value":4661},"raw.klaviyo_campaigns",{"type":43,"tag":73,"props":4663,"children":4664},{},[4665,4666,4672,4674],{"type":49,"value":4566},{"type":43,"tag":310,"props":4667,"children":4669},{"className":4668},[],[4670],{"type":49,"value":4671},"send_time",{"type":49,"value":4673}," to TIMESTAMP and derive ",{"type":43,"tag":310,"props":4675,"children":4677},{"className":4676},[],[4678],{"type":49,"value":4679},"send_date = send_time::DATE",{"type":43,"tag":73,"props":4681,"children":4682},{},[4683],{"type":49,"value":4684},"Dedupe on campaign_id (keep the most recent snapshot)",{"type":43,"tag":73,"props":4686,"children":4687},{},[4688,4690,4696,4697,4703,4704],{"type":49,"value":4689},"Derived metrics: ",{"type":43,"tag":310,"props":4691,"children":4693},{"className":4692},[],[4694],{"type":49,"value":4695},"open_rate = opens / NULLIF(recipients, 0)",{"type":49,"value":451},{"type":43,"tag":310,"props":4698,"children":4700},{"className":4699},[],[4701],{"type":49,"value":4702},"click_rate = clicks / NULLIF(recipients, 0)",{"type":49,"value":451},{"type":43,"tag":310,"props":4705,"children":4707},{"className":4706},[],[4708],{"type":49,"value":4709},"revenue_per_recipient = revenue / NULLIF(recipients, 0)",{"type":43,"tag":73,"props":4711,"children":4712},{},[4713,4718,4720,4726,4728,4734,4736,4742],{"type":43,"tag":77,"props":4714,"children":4715},{},[4716],{"type":49,"value":4717},"Add a comment",{"type":49,"value":4719}," noting that ",{"type":43,"tag":310,"props":4721,"children":4723},{"className":4722},[],[4724],{"type":49,"value":4725},"opens",{"type":49,"value":4727}," are unreliable on iOS; prefer ",{"type":43,"tag":310,"props":4729,"children":4731},{"className":4730},[],[4732],{"type":49,"value":4733},"clicks",{"type":49,"value":4735}," or ",{"type":43,"tag":310,"props":4737,"children":4739},{"className":4738},[],[4740],{"type":49,"value":4741},"revenue",{"type":49,"value":4743}," for engagement signals",{"type":43,"tag":73,"props":4745,"children":4746},{},[4747],{"type":49,"value":4748},"Keep: campaign_id, send_time, send_date, recipients, opens, clicks, unsubscribes, revenue, open_rate, click_rate, revenue_per_recipient",{"type":43,"tag":52,"props":4750,"children":4751},{},[4752,4761,4762],{"type":43,"tag":77,"props":4753,"children":4754},{},[4755],{"type":43,"tag":310,"props":4756,"children":4758},{"className":4757},[],[4759],{"type":49,"value":4760},"staging/ga4.sql",{"type":49,"value":4541},{"type":43,"tag":310,"props":4763,"children":4765},{"className":4764},[],[4766],{"type":49,"value":4767},"staging.ga4",{"type":43,"tag":69,"props":4769,"children":4770},{},[4771,4781,4792,4797,4850,4861],{"type":43,"tag":73,"props":4772,"children":4773},{},[4774,4775],{"type":49,"value":4555},{"type":43,"tag":310,"props":4776,"children":4778},{"className":4777},[],[4779],{"type":49,"value":4780},"raw.ga4_traffic",{"type":43,"tag":73,"props":4782,"children":4783},{},[4784,4785,4790],{"type":49,"value":4566},{"type":43,"tag":310,"props":4786,"children":4788},{"className":4787},[],[4789],{"type":49,"value":4572},{"type":49,"value":4791}," to DATE",{"type":43,"tag":73,"props":4793,"children":4794},{},[4795],{"type":49,"value":4796},"Dedupe on (date, sessionSource, sessionMedium)",{"type":43,"tag":73,"props":4798,"children":4799},{},[4800,4802,4808,4810,4816,4818,4824,4826,4832,4834,4840,4842,4848],{"type":49,"value":4801},"Classify ",{"type":43,"tag":310,"props":4803,"children":4805},{"className":4804},[],[4806],{"type":49,"value":4807},"channel",{"type":49,"value":4809}," from medium: ",{"type":43,"tag":310,"props":4811,"children":4813},{"className":4812},[],[4814],{"type":49,"value":4815},"cpc",{"type":49,"value":4817}," → 'Paid Search', ",{"type":43,"tag":310,"props":4819,"children":4821},{"className":4820},[],[4822],{"type":49,"value":4823},"organic",{"type":49,"value":4825}," → 'Organic', ",{"type":43,"tag":310,"props":4827,"children":4829},{"className":4828},[],[4830],{"type":49,"value":4831},"email",{"type":49,"value":4833}," → 'Email', ",{"type":43,"tag":310,"props":4835,"children":4837},{"className":4836},[],[4838],{"type":49,"value":4839},"(none)",{"type":49,"value":4841}," / ",{"type":43,"tag":310,"props":4843,"children":4845},{"className":4844},[],[4846],{"type":49,"value":4847},"direct",{"type":49,"value":4849}," → 'Direct', everything else → 'Other'",{"type":43,"tag":73,"props":4851,"children":4852},{},[4853,4855],{"type":49,"value":4854},"Derived: ",{"type":43,"tag":310,"props":4856,"children":4858},{"className":4857},[],[4859],{"type":49,"value":4860},"cvr = conversions / NULLIF(sessions, 0)",{"type":43,"tag":73,"props":4862,"children":4863},{},[4864],{"type":49,"value":4865},"Keep: date, sessionSource, sessionMedium, channel, sessions, totalUsers, conversions, purchaseRevenue, cvr",{"type":43,"tag":52,"props":4867,"children":4868},{},[4869,4871,4877,4879,4885,4887,4893],{"type":49,"value":4870},"For each asset, add a top-level ",{"type":43,"tag":310,"props":4872,"children":4874},{"className":4873},[],[4875],{"type":49,"value":4876},"description:",{"type":49,"value":4878},", column descriptions, and at least one quality check (e.g. ",{"type":43,"tag":310,"props":4880,"children":4882},{"className":4881},[],[4883],{"type":49,"value":4884},"not_null",{"type":49,"value":4886}," on the grain columns, ",{"type":43,"tag":310,"props":4888,"children":4890},{"className":4889},[],[4891],{"type":49,"value":4892},"unique",{"type":49,"value":4894}," on the dedupe key).",{"type":43,"tag":52,"props":4896,"children":4897},{},[4898],{"type":49,"value":4899},"Show me the SQL for each asset before writing the files. Don't run anything yet - I'll approve first.",{"type":43,"tag":52,"props":4901,"children":4902},{},[4903,4905,4910,4911,4916],{"type":49,"value":4904},"Read each SQL file the agent produces. Look specifically for the unit conversion (",{"type":43,"tag":310,"props":4906,"children":4908},{"className":4907},[],[4909],{"type":49,"value":4421},{"type":49,"value":4541},{"type":43,"tag":310,"props":4912,"children":4914},{"className":4913},[],[4915],{"type":49,"value":4597},{"type":49,"value":4917},"), the dedup logic, and the channel classification. If anything looks off, push back - it's cheaper to correct here than downstream.",{"type":43,"tag":44,"props":4919,"children":4921},{"id":4920},"_2-scaffold-the-reports-table",[4922],{"type":49,"value":4923},"2. Scaffold the reports table",{"type":43,"tag":52,"props":4925,"children":4926},{},[4927],{"type":49,"value":952},{"type":43,"tag":301,"props":4929,"children":4930},{},[4931,4950,4969,5120,5162],{"type":43,"tag":52,"props":4932,"children":4933},{},[4934,4936,4941,4943,4949],{"type":49,"value":4935},"Now create one Bruin SQL asset for the reports layer, materialized as a ",{"type":43,"tag":77,"props":4937,"children":4938},{},[4939],{"type":49,"value":4940},"table",{"type":49,"value":4942}," with schema ",{"type":43,"tag":310,"props":4944,"children":4946},{"className":4945},[],[4947],{"type":49,"value":4948},"reports",{"type":49,"value":595},{"type":43,"tag":52,"props":4951,"children":4952},{},[4953,4962,4963],{"type":43,"tag":77,"props":4954,"children":4955},{},[4956],{"type":43,"tag":310,"props":4957,"children":4959},{"className":4958},[],[4960],{"type":49,"value":4961},"reports/channel_daily.sql",{"type":49,"value":4541},{"type":43,"tag":310,"props":4964,"children":4966},{"className":4965},[],[4967],{"type":49,"value":4968},"reports.channel_daily",{"type":43,"tag":69,"props":4970,"children":4971},{},[4972,4977,5077,5107],{"type":43,"tag":73,"props":4973,"children":4974},{},[4975],{"type":49,"value":4976},"One row per (channel, date) for the last 90 days",{"type":43,"tag":73,"props":4978,"children":4979},{},[4980,4982,4986,4991,4993,4998,5000,5008,5011,5016,5017,5022,5024,5030,5032,5037,5038,5046,5049,5054,5055,5060,5062,5068,5069],{"type":49,"value":4981},"Union three channel streams:",{"type":43,"tag":4983,"props":4984,"children":4985},"br",{},[],{"type":43,"tag":77,"props":4987,"children":4988},{},[4989],{"type":49,"value":4990},"Paid Search",{"type":49,"value":4992}," (from ",{"type":43,"tag":310,"props":4994,"children":4996},{"className":4995},[],[4997],{"type":49,"value":4547},{"type":49,"value":4999},"):",{"type":43,"tag":69,"props":5001,"children":5002},{},[5003],{"type":43,"tag":73,"props":5004,"children":5005},{},[5006],{"type":49,"value":5007},"channel = 'Paid Search', date, cost_usd, clicks, conversions, attributed_revenue = conversion_value, sessions = clicks (proxy)",{"type":43,"tag":4983,"props":5009,"children":5010},{},[],{"type":43,"tag":77,"props":5012,"children":5013},{},[5014],{"type":49,"value":5015},"Email",{"type":49,"value":4992},{"type":43,"tag":310,"props":5018,"children":5020},{"className":5019},[],[5021],{"type":49,"value":4648},{"type":49,"value":5023},", using ",{"type":43,"tag":310,"props":5025,"children":5027},{"className":5026},[],[5028],{"type":49,"value":5029},"send_date",{"type":49,"value":5031}," as ",{"type":43,"tag":310,"props":5033,"children":5035},{"className":5034},[],[5036],{"type":49,"value":4572},{"type":49,"value":4999},{"type":43,"tag":69,"props":5039,"children":5040},{},[5041],{"type":43,"tag":73,"props":5042,"children":5043},{},[5044],{"type":49,"value":5045},"channel = 'Email', cost_usd = 0, clicks, conversions = NULL (Klaviyo doesn't report conversions this way), attributed_revenue = revenue, sessions = recipients (proxy for audience size)",{"type":43,"tag":4983,"props":5047,"children":5048},{},[],{"type":43,"tag":77,"props":5050,"children":5051},{},[5052],{"type":49,"value":5053},"Organic",{"type":49,"value":4992},{"type":43,"tag":310,"props":5056,"children":5058},{"className":5057},[],[5059],{"type":49,"value":4767},{"type":49,"value":5061}," where ",{"type":43,"tag":310,"props":5063,"children":5065},{"className":5064},[],[5066],{"type":49,"value":5067},"channel = 'Organic'",{"type":49,"value":4999},{"type":43,"tag":69,"props":5070,"children":5071},{},[5072],{"type":43,"tag":73,"props":5073,"children":5074},{},[5075],{"type":49,"value":5076},"channel = 'Organic', cost_usd = 0, clicks = NULL, conversions, attributed_revenue = purchaseRevenue, sessions",{"type":43,"tag":73,"props":5078,"children":5079},{},[5080,5082],{"type":49,"value":5081},"After the union, compute per row:",{"type":43,"tag":69,"props":5083,"children":5084},{},[5085,5096],{"type":43,"tag":73,"props":5086,"children":5087},{},[5088,5094],{"type":43,"tag":310,"props":5089,"children":5091},{"className":5090},[],[5092],{"type":49,"value":5093},"cac_usd",{"type":49,"value":5095}," = cost_usd / NULLIF(conversions, 0) - NULL for channels without conversion counts",{"type":43,"tag":73,"props":5097,"children":5098},{},[5099,5105],{"type":43,"tag":310,"props":5100,"children":5102},{"className":5101},[],[5103],{"type":49,"value":5104},"roas",{"type":49,"value":5106}," = attributed_revenue / NULLIF(cost_usd, 0) - NULL / infinite for cost=0 channels (set to NULL when cost_usd \u003C= 0)",{"type":43,"tag":73,"props":5108,"children":5109},{},[5110,5112,5118],{"type":49,"value":5111},"Add ",{"type":43,"tag":310,"props":5113,"children":5115},{"className":5114},[],[5116],{"type":49,"value":5117},"data_caveat",{"type":49,"value":5119}," column with a short string noting the attribution assumptions (e.g. 'Klaviyo revenue uses Klaviyo's 5-day click attribution')",{"type":43,"tag":52,"props":5121,"children":5122},{},[5123,5125,5131,5132,5138,5140,5145,5147,5153,5155,5160],{"type":49,"value":5124},"Add asset-level description that makes the attribution model explicit, column descriptions, tags (",{"type":43,"tag":310,"props":5126,"children":5128},{"className":5127},[],[5129],{"type":49,"value":5130},"tier:reports",{"type":49,"value":451},{"type":43,"tag":310,"props":5133,"children":5135},{"className":5134},[],[5136],{"type":49,"value":5137},"domain:marketing",{"type":49,"value":5139},"), and quality checks (",{"type":43,"tag":310,"props":5141,"children":5143},{"className":5142},[],[5144],{"type":49,"value":4884},{"type":49,"value":5146}," on channel + date, ",{"type":43,"tag":310,"props":5148,"children":5150},{"className":5149},[],[5151],{"type":49,"value":5152},"accepted_values",{"type":49,"value":5154}," on channel = ",{"type":43,"tag":412,"props":5156,"children":5157},{},[5158],{"type":49,"value":5159},"'Paid Search', 'Email', 'Organic'",{"type":49,"value":5161},").",{"type":43,"tag":52,"props":5163,"children":5164},{},[5165],{"type":49,"value":5166},"Show me the SQL before writing. I'll approve before you run anything.",{"type":43,"tag":52,"props":5168,"children":5169},{},[5170],{"type":49,"value":5171},"This is the analysis-ready cross-channel surface. Every question in Step 6 starts here.",{"type":43,"tag":44,"props":5173,"children":5175},{"id":5174},"_3-run-and-spot-check",[5176],{"type":49,"value":5177},"3. Run and spot-check",{"type":43,"tag":52,"props":5179,"children":5180},{},[5181],{"type":49,"value":952},{"type":43,"tag":301,"props":5183,"children":5184},{},[5185,5204,5216,5279],{"type":43,"tag":52,"props":5186,"children":5187},{},[5188,5189,5195,5197,5202],{"type":49,"value":913},{"type":43,"tag":310,"props":5190,"children":5192},{"className":5191},[],[5193],{"type":49,"value":5194},"bruin validate marketing-analyst-101/",{"type":49,"value":5196}," first. If it passes, run ",{"type":43,"tag":310,"props":5198,"children":5200},{"className":5199},[],[5201],{"type":49,"value":919},{"type":49,"value":5203}," and stream the output. Tell me which assets succeeded and which failed.",{"type":43,"tag":52,"props":5205,"children":5206},{},[5207,5209,5215],{"type":49,"value":5208},"After it completes, do spot checks across all three layers using ",{"type":43,"tag":310,"props":5210,"children":5212},{"className":5211},[],[5213],{"type":49,"value":5214},"bruin query --connection duckdb-default",{"type":49,"value":325},{"type":43,"tag":327,"props":5217,"children":5218},{},[5219,5240,5253,5266],{"type":43,"tag":73,"props":5220,"children":5221},{},[5222,5224,5230,5232,5238],{"type":49,"value":5223},"Unit sanity: ",{"type":43,"tag":310,"props":5225,"children":5227},{"className":5226},[],[5228],{"type":49,"value":5229},"SELECT SUM(cost_micros)/1e6 FROM raw.google_ads_campaigns",{"type":49,"value":5231}," vs ",{"type":43,"tag":310,"props":5233,"children":5235},{"className":5234},[],[5236],{"type":49,"value":5237},"SELECT SUM(cost_usd) FROM staging.google_ads",{"type":49,"value":5239}," - should match within a penny",{"type":43,"tag":73,"props":5241,"children":5242},{},[5243,5245,5251],{"type":49,"value":5244},"Dedup audit: ",{"type":43,"tag":310,"props":5246,"children":5248},{"className":5247},[],[5249],{"type":49,"value":5250},"SELECT campaign_id, date, COUNT(*) FROM staging.google_ads GROUP BY 1,2 HAVING COUNT(*) > 1",{"type":49,"value":5252}," - should return 0 rows",{"type":43,"tag":73,"props":5254,"children":5255},{},[5256,5258,5264],{"type":49,"value":5257},"Channel coverage: ",{"type":43,"tag":310,"props":5259,"children":5261},{"className":5260},[],[5262],{"type":49,"value":5263},"SELECT channel, COUNT(*), MIN(date), MAX(date), SUM(cost_usd), SUM(attributed_revenue) FROM reports.channel_daily GROUP BY channel",{"type":49,"value":5265}," - all three channels should appear with sensible totals",{"type":43,"tag":73,"props":5267,"children":5268},{},[5269,5271,5277],{"type":49,"value":5270},"CAC sanity: ",{"type":43,"tag":310,"props":5272,"children":5274},{"className":5273},[],[5275],{"type":49,"value":5276},"SELECT channel, AVG(cac_usd) FROM reports.channel_daily WHERE cac_usd IS NOT NULL GROUP BY channel",{"type":49,"value":5278}," - CAC should be positive and in the \"tens to hundreds\" USD range for most consumer businesses",{"type":43,"tag":52,"props":5280,"children":5281},{},[5282],{"type":49,"value":5283},"Show me the results before moving on.",{"type":43,"tag":52,"props":5285,"children":5286},{},[5287,5289,5294],{"type":49,"value":5288},"You should see three channels in ",{"type":43,"tag":310,"props":5290,"children":5292},{"className":5291},[],[5293],{"type":49,"value":4968},{"type":49,"value":5295},", spanning roughly 90 days, with coherent CAC and ROAS numbers.",{"type":43,"tag":44,"props":5297,"children":5299},{"id":5298},"_4-why-the-layering-matters",[5300],{"type":49,"value":5301},"4. Why the layering matters",{"type":43,"tag":52,"props":5303,"children":5304},{},[5305],{"type":49,"value":5306},"Now you have a clean three-tier pipeline:",{"type":43,"tag":687,"props":5308,"children":5311},{"className":5309,"code":5310,"language":49},[2741],"raw.*                      staging.*                   reports.*\n──────────────────         ──────────────────          ──────────────────\ngoogle_ads_campaigns →     google_ads          ┐\nklaviyo_campaigns    →     klaviyo             ├──→   channel_daily\nga4_traffic          →     ga4                 ┘\n(source truth)             (cleaned + typed)           (analysis-ready)\n",[5312],{"type":43,"tag":310,"props":5313,"children":5314},{"__ignoreMap":7},[5315],{"type":49,"value":5310},{"type":43,"tag":52,"props":5317,"children":5318},{},[5319,5321,5329,5331,5336],{"type":49,"value":5320},"Every question you ask in Step 6 goes against ",{"type":43,"tag":77,"props":5322,"children":5323},{},[5324],{"type":43,"tag":310,"props":5325,"children":5327},{"className":5326},[],[5328],{"type":49,"value":4968},{"type":49,"value":5330}," first. The AI agent doesn't have to remember that ",{"type":43,"tag":310,"props":5332,"children":5334},{"className":5333},[],[5335],{"type":49,"value":4421},{"type":49,"value":5337}," needs dividing, or that Klaviyo revenue uses a different attribution window than GA4. Raw and staging exist for validation and troubleshooting, not for everyday analysis.",{"type":43,"tag":52,"props":5339,"children":5340},{},[5341,5343,5348,5350,5355],{"type":49,"value":5342},"In the next step, you'll enhance each layer with column-level descriptions and update your pipeline-scoped ",{"type":43,"tag":310,"props":5344,"children":5346},{"className":5345},[],[5347],{"type":49,"value":2562},{"type":49,"value":5349}," so the agent knows to default to ",{"type":43,"tag":310,"props":5351,"children":5353},{"className":5352},[],[5354],{"type":49,"value":4402},{"type":49,"value":595},{"type":43,"tag":44,"props":5357,"children":5358},{"id":1070},[5359],{"type":49,"value":1073},{"type":43,"tag":52,"props":5361,"children":5362},{},[5363],{"type":49,"value":5364},"You built a proper layered pipeline - raw → staging → reports - without writing any SQL by hand. The agent wrote every asset, you validated the unit conversions and dedup, Bruin ran the whole pipeline. Your data is cleaned, typed, deduped, unit-normalized, and joined into one cross-channel surface. Next step: make sure the AI knows which layer to query when.",{"title":7,"searchDepth":721,"depth":721,"links":5366},[5367,5368,5369,5370,5371,5372,5373],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":4492,"depth":721,"text":4495},{"id":4920,"depth":721,"text":4923},{"id":5174,"depth":721,"text":5177},{"id":5298,"depth":721,"text":5301},{"id":1070,"depth":721,"text":1073},"content:tutorials:marketing-analyst-101:transform-reports.md","tutorials/marketing-analyst-101/transform-reports.md","tutorials/marketing-analyst-101/transform-reports",{"_path":5378,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":5379,"description":5380,"date":10,"readingTime":860,"category":12,"tags":5381,"difficulty":18,"module":5,"step":770,"journeys":5382,"roles":5383,"learnMore":5384,"author":5391,"body":5392,"_type":1103,"_id":6500,"_source":1105,"_file":6501,"_stem":6502,"_extension":1108},"/tutorials/marketing-analyst-101/enhance-context","Teach the AI Your Domain","Use bruin ai enhance to document every column, then add a pipeline-scoped AGENTS.md with marketing context so the agent reasons about attribution, CAC, and LTV like a real analyst.",[14,15,1969,16,17],[21],[23,24],[5385,5388],{"label":5386,"url":5387},"bruin ai enhance reference","https://getbruin.com/docs/bruin/commands/ai-enhance.html",{"label":5389,"url":5390},"Bruin MCP best practices","https://getbruin.com/docs/bruin/getting-started/bruin-mcp.html#best-practices-for-ai-agents",{"name":36,"role":37,"image":38},{"type":40,"children":5393,"toc":6492},[5394,5398,5436,5440,5460,5465,5526,5538,5544,5548,5597,5617,5802,5807,5815,5821,5860,5864,6395,6414,6420,6431,6446,6465,6469,6488],{"type":43,"tag":44,"props":5395,"children":5396},{"id":46},[5397],{"type":49,"value":50},{"type":43,"tag":327,"props":5399,"children":5400},{},[5401,5413],{"type":43,"tag":73,"props":5402,"children":5403},{},[5404,5405,5411],{"type":49,"value":913},{"type":43,"tag":310,"props":5406,"children":5408},{"className":5407},[],[5409],{"type":49,"value":5410},"bruin ai enhance",{"type":49,"value":5412}," so the agent auto-documents every table and column",{"type":43,"tag":73,"props":5414,"children":5415},{},[5416,5418,5428,5429,5434],{"type":49,"value":5417},"Create a ",{"type":43,"tag":77,"props":5419,"children":5420},{},[5421,5423],{"type":49,"value":5422},"pipeline-specific ",{"type":43,"tag":310,"props":5424,"children":5426},{"className":5425},[],[5427],{"type":49,"value":2562},{"type":49,"value":3445},{"type":43,"tag":310,"props":5430,"children":5432},{"className":5431},[],[5433],{"type":49,"value":3451},{"type":49,"value":5435}," folder with marketing-specific domain knowledge, attribution rules, and query guidelines",{"type":43,"tag":44,"props":5437,"children":5438},{"id":59},[5439],{"type":49,"value":62},{"type":43,"tag":52,"props":5441,"children":5442},{},[5443,5445,5450,5452,5458],{"type":49,"value":5444},"Data alone is not enough. An AI agent looking at a column called ",{"type":43,"tag":310,"props":5446,"children":5448},{"className":5447},[],[5449],{"type":49,"value":4421},{"type":49,"value":5451}," will guess \"cost, in some unit\" - fine for casual questions, wrong for CAC math (it's cost × 1,000,000, so $5.00 is stored as ",{"type":43,"tag":310,"props":5453,"children":5455},{"className":5454},[],[5456],{"type":49,"value":5457},"5000000",{"type":49,"value":5459},"). Your job in this step is to make sure the agent never has to guess about units, time grains, or attribution logic.",{"type":43,"tag":52,"props":5461,"children":5462},{},[5463],{"type":49,"value":5464},"You're giving the agent three layers of context now:",{"type":43,"tag":69,"props":5466,"children":5467},{},[5468,5485,5501],{"type":43,"tag":73,"props":5469,"children":5470},{},[5471,5476,5478,5483],{"type":43,"tag":77,"props":5472,"children":5473},{},[5474],{"type":49,"value":5475},"Workspace-level rules",{"type":49,"value":5477}," - the root ",{"type":43,"tag":310,"props":5479,"children":5481},{"className":5480},[],[5482],{"type":49,"value":2562},{"type":49,"value":5484}," from Step 2 tells the agent how to work with Bruin (CLI, validate, test limits, etc.) across any pipeline in this workspace.",{"type":43,"tag":73,"props":5486,"children":5487},{},[5488,5493,5495,5500],{"type":43,"tag":77,"props":5489,"children":5490},{},[5491],{"type":49,"value":5492},"Schema context",{"type":49,"value":5494}," - what each table and column is. Auto-generated in seconds with ",{"type":43,"tag":310,"props":5496,"children":5498},{"className":5497},[],[5499],{"type":49,"value":5410},{"type":49,"value":595},{"type":43,"tag":73,"props":5502,"children":5503},{},[5504,5509,5511,5516,5518,5524],{"type":43,"tag":77,"props":5505,"children":5506},{},[5507],{"type":49,"value":5508},"Pipeline-specific domain context",{"type":49,"value":5510}," - how a marketing analyst thinks about attribution, CAC, LTV, and cross-channel blending for ",{"type":43,"tag":203,"props":5512,"children":5513},{},[5514],{"type":49,"value":5515},"this",{"type":49,"value":5517}," pipeline. Lives next to the pipeline as ",{"type":43,"tag":310,"props":5519,"children":5521},{"className":5520},[],[5522],{"type":49,"value":5523},"marketing-analyst-101/AGENTS.md",{"type":49,"value":5525}," so it travels with it and doesn't leak into other pipelines.",{"type":43,"tag":52,"props":5527,"children":5528},{},[5529,5531,5536],{"type":49,"value":5530},"AI coding tools (Claude Code, Cursor, Codex) read nested ",{"type":43,"tag":310,"props":5532,"children":5534},{"className":5533},[],[5535],{"type":49,"value":2562},{"type":49,"value":5537}," files when they work inside that subfolder, so the pipeline-scoped file automatically layers on top of the workspace-level one. Together these turn the agent from \"eager intern\" into \"actual analyst.\"",{"type":43,"tag":44,"props":5539,"children":5541},{"id":5540},"_1-auto-enhance-schema-context",[5542],{"type":49,"value":5543},"1. Auto-enhance schema context",{"type":43,"tag":52,"props":5545,"children":5546},{},[5547],{"type":49,"value":952},{"type":43,"tag":301,"props":5549,"children":5550},{},[5551],{"type":43,"tag":52,"props":5552,"children":5553},{},[5554,5555,5561,5563,5568,5569,5574,5576,5581,5583,5588,5590,5595],{"type":49,"value":913},{"type":43,"tag":310,"props":5556,"children":5558},{"className":5557},[],[5559],{"type":49,"value":5560},"bruin ai enhance marketing-analyst-101/",{"type":49,"value":5562}," and let me know when it's done. It should enhance assets across all three layers - ",{"type":43,"tag":310,"props":5564,"children":5566},{"className":5565},[],[5567],{"type":49,"value":932},{"type":49,"value":451},{"type":43,"tag":310,"props":5570,"children":5572},{"className":5571},[],[5573],{"type":49,"value":4386},{"type":49,"value":5575},", and ",{"type":43,"tag":310,"props":5577,"children":5579},{"className":5578},[],[5580],{"type":49,"value":4402},{"type":49,"value":5582},". Then show me the changes it made to ",{"type":43,"tag":310,"props":5584,"children":5586},{"className":5585},[],[5587],{"type":49,"value":561},{"type":49,"value":5589}," (raw) and ",{"type":43,"tag":310,"props":5591,"children":5593},{"className":5592},[],[5594],{"type":49,"value":4961},{"type":49,"value":5596}," (reports) so I can spot-check the descriptions.",{"type":43,"tag":52,"props":5598,"children":5599},{},[5600,5602,5607,5609,5615],{"type":49,"value":5601},"This command looks at each table, asks an LLM to describe what it sees, and writes column descriptions directly into the asset YAML - across all three layers. After it runs, your ",{"type":43,"tag":310,"props":5603,"children":5605},{"className":5604},[],[5606],{"type":49,"value":561},{"type":49,"value":5608}," will have a ",{"type":43,"tag":310,"props":5610,"children":5612},{"className":5611},[],[5613],{"type":49,"value":5614},"columns:",{"type":49,"value":5616}," block like:",{"type":43,"tag":687,"props":5618,"children":5620},{"className":689,"code":5619,"language":691,"meta":7,"style":7},"columns:\n  - name: campaign_id\n    type: bigint\n    description: \"Google Ads campaign ID\"\n  - name: date\n    type: date\n    description: \"Date of the campaign stats (UTC)\"\n  - name: cost_micros\n    type: bigint\n    description: \"Cost in micros - divide by 1,000,000 for USD\"\n  # ...many more\n",[5621],{"type":43,"tag":310,"props":5622,"children":5623},{"__ignoreMap":7},[5624,5636,5657,5674,5691,5711,5726,5742,5762,5777,5793],{"type":43,"tag":412,"props":5625,"children":5626},{"class":698,"line":699},[5627,5632],{"type":43,"tag":412,"props":5628,"children":5629},{"style":703},[5630],{"type":49,"value":5631},"columns",{"type":43,"tag":412,"props":5633,"children":5634},{"style":709},[5635],{"type":49,"value":749},{"type":43,"tag":412,"props":5637,"children":5638},{"class":698,"line":721},[5639,5644,5648,5652],{"type":43,"tag":412,"props":5640,"children":5641},{"style":709},[5642],{"type":49,"value":5643},"  - ",{"type":43,"tag":412,"props":5645,"children":5646},{"style":703},[5647],{"type":49,"value":706},{"type":43,"tag":412,"props":5649,"children":5650},{"style":709},[5651],{"type":49,"value":712},{"type":43,"tag":412,"props":5653,"children":5654},{"style":715},[5655],{"type":49,"value":5656},"campaign_id\n",{"type":43,"tag":412,"props":5658,"children":5659},{"class":698,"line":19},[5660,5665,5669],{"type":43,"tag":412,"props":5661,"children":5662},{"style":703},[5663],{"type":49,"value":5664},"    type",{"type":43,"tag":412,"props":5666,"children":5667},{"style":709},[5668],{"type":49,"value":712},{"type":43,"tag":412,"props":5670,"children":5671},{"style":715},[5672],{"type":49,"value":5673},"bigint\n",{"type":43,"tag":412,"props":5675,"children":5676},{"class":698,"line":752},[5677,5682,5686],{"type":43,"tag":412,"props":5678,"children":5679},{"style":703},[5680],{"type":49,"value":5681},"    description",{"type":43,"tag":412,"props":5683,"children":5684},{"style":709},[5685],{"type":49,"value":712},{"type":43,"tag":412,"props":5687,"children":5688},{"style":715},[5689],{"type":49,"value":5690},"\"Google Ads campaign ID\"\n",{"type":43,"tag":412,"props":5692,"children":5693},{"class":698,"line":770},[5694,5698,5702,5706],{"type":43,"tag":412,"props":5695,"children":5696},{"style":709},[5697],{"type":49,"value":5643},{"type":43,"tag":412,"props":5699,"children":5700},{"style":703},[5701],{"type":49,"value":706},{"type":43,"tag":412,"props":5703,"children":5704},{"style":709},[5705],{"type":49,"value":712},{"type":43,"tag":412,"props":5707,"children":5708},{"style":715},[5709],{"type":49,"value":5710},"date\n",{"type":43,"tag":412,"props":5712,"children":5713},{"class":698,"line":788},[5714,5718,5722],{"type":43,"tag":412,"props":5715,"children":5716},{"style":703},[5717],{"type":49,"value":5664},{"type":43,"tag":412,"props":5719,"children":5720},{"style":709},[5721],{"type":49,"value":712},{"type":43,"tag":412,"props":5723,"children":5724},{"style":715},[5725],{"type":49,"value":5710},{"type":43,"tag":412,"props":5727,"children":5728},{"class":698,"line":806},[5729,5733,5737],{"type":43,"tag":412,"props":5730,"children":5731},{"style":703},[5732],{"type":49,"value":5681},{"type":43,"tag":412,"props":5734,"children":5735},{"style":709},[5736],{"type":49,"value":712},{"type":43,"tag":412,"props":5738,"children":5739},{"style":715},[5740],{"type":49,"value":5741},"\"Date of the campaign stats (UTC)\"\n",{"type":43,"tag":412,"props":5743,"children":5744},{"class":698,"line":824},[5745,5749,5753,5757],{"type":43,"tag":412,"props":5746,"children":5747},{"style":709},[5748],{"type":49,"value":5643},{"type":43,"tag":412,"props":5750,"children":5751},{"style":703},[5752],{"type":49,"value":706},{"type":43,"tag":412,"props":5754,"children":5755},{"style":709},[5756],{"type":49,"value":712},{"type":43,"tag":412,"props":5758,"children":5759},{"style":715},[5760],{"type":49,"value":5761},"cost_micros\n",{"type":43,"tag":412,"props":5763,"children":5764},{"class":698,"line":842},[5765,5769,5773],{"type":43,"tag":412,"props":5766,"children":5767},{"style":703},[5768],{"type":49,"value":5664},{"type":43,"tag":412,"props":5770,"children":5771},{"style":709},[5772],{"type":49,"value":712},{"type":43,"tag":412,"props":5774,"children":5775},{"style":715},[5776],{"type":49,"value":5673},{"type":43,"tag":412,"props":5778,"children":5779},{"class":698,"line":860},[5780,5784,5788],{"type":43,"tag":412,"props":5781,"children":5782},{"style":703},[5783],{"type":49,"value":5681},{"type":43,"tag":412,"props":5785,"children":5786},{"style":709},[5787],{"type":49,"value":712},{"type":43,"tag":412,"props":5789,"children":5790},{"style":715},[5791],{"type":49,"value":5792},"\"Cost in micros - divide by 1,000,000 for USD\"\n",{"type":43,"tag":412,"props":5794,"children":5795},{"class":698,"line":3068},[5796],{"type":43,"tag":412,"props":5797,"children":5799},{"style":5798},"--shiki-default:#6A737D",[5800],{"type":49,"value":5801},"  # ...many more\n",{"type":43,"tag":52,"props":5803,"children":5804},{},[5805],{"type":49,"value":5806},"These descriptions become part of the MCP context the agent reads before every query. You don't need to memorize any of this - the agent will look it up when it needs to.",{"type":43,"tag":189,"props":5808,"children":5809},{},[5810],{"type":43,"tag":52,"props":5811,"children":5812},{},[5813],{"type":49,"value":5814},"If the agent got a description wrong (e.g. it misread a column meaning), ask it to fix that specific line. The YAML is yours to edit.",{"type":43,"tag":44,"props":5816,"children":5818},{"id":5817},"_2-create-a-pipeline-specific-agentsmd",[5819],{"type":49,"value":5820},"2. Create a pipeline-specific AGENTS.md",{"type":43,"tag":52,"props":5822,"children":5823},{},[5824,5826,5831,5833,5838,5840,5845,5847,5852,5853,5858],{"type":49,"value":5825},"The root ",{"type":43,"tag":310,"props":5827,"children":5829},{"className":5828},[],[5830],{"type":49,"value":2562},{"type":49,"value":5832}," you seeded in Step 2 covers ",{"type":43,"tag":203,"props":5834,"children":5835},{},[5836],{"type":49,"value":5837},"how",{"type":49,"value":5839}," the agent should work with Bruin in general. This new file covers ",{"type":43,"tag":203,"props":5841,"children":5842},{},[5843],{"type":49,"value":5844},"what",{"type":49,"value":5846}," this specific pipeline's data means - glossary, attribution rules, unit caveats. It lives ",{"type":43,"tag":77,"props":5848,"children":5849},{},[5850],{"type":49,"value":5851},"inside the pipeline folder",{"type":49,"value":2289},{"type":43,"tag":310,"props":5854,"children":5856},{"className":5855},[],[5857],{"type":49,"value":5523},{"type":49,"value":5859},") so the marketing-specific context stays scoped to the pipeline it belongs to - if you later add a different pipeline in the same workspace, you won't pollute it with marketing glossary entries.",{"type":43,"tag":52,"props":5861,"children":5862},{},[5863],{"type":49,"value":299},{"type":43,"tag":301,"props":5865,"children":5866},{},[5867,5893],{"type":43,"tag":52,"props":5868,"children":5869},{},[5870,5872,5877,5879,5884,5886,5891],{"type":49,"value":5871},"Create a new file at ",{"type":43,"tag":310,"props":5873,"children":5875},{"className":5874},[],[5876],{"type":49,"value":5523},{"type":49,"value":5878}," (inside the pipeline folder - ",{"type":43,"tag":77,"props":5880,"children":5881},{},[5882],{"type":49,"value":5883},"not",{"type":49,"value":5885}," the workspace root) with the content below. Do not modify the root ",{"type":43,"tag":310,"props":5887,"children":5889},{"className":5888},[],[5890],{"type":49,"value":2562},{"type":49,"value":5892},"; the pipeline-specific file layers on top of it automatically when the agent works inside the pipeline folder. After you're done, show me the file.",{"type":43,"tag":687,"props":5894,"children":5896},{"className":2981,"code":5895,"language":1103,"meta":7,"style":7},"# AGENTS.md - marketing-analyst-101\n\n## Pipeline overview\nThis is a marketing-analyst research pipeline. Data lives in DuckDB\n(`./marketing-analyst-101/marketing.duckdb`), organized in three layers:\n\n**Raw layer** (`raw.*`) - unmodified from source:\n- `raw.google_ads_campaigns` - daily campaign-level ad spend and conversions (from Google Ads). `cost_micros` is in **millionths of a dollar**.\n- `raw.klaviyo_campaigns` - campaign-level email metrics and attributed revenue (from Klaviyo)\n- `raw.ga4_traffic` - daily sessions by source/medium with conversion events (from GA4)\n\n**Staging layer** (`staging.*`) - cleaned, typed, deduplicated, unit-normalized:\n- `staging.google_ads` - adds `cost_usd`, `ctr`, `cvr`, `cpc_usd`; deduped on (campaign_id, date)\n- `staging.klaviyo` - adds `open_rate`, `click_rate`, `revenue_per_recipient`; deduped on campaign_id\n- `staging.ga4` - adds a normalized `channel` column (Paid Search / Organic / Email / Direct / Other) and `cvr`\n\n**Reports layer** (`reports.*`) - analysis-ready cross-channel surface:\n- `reports.channel_daily` - one row per (channel, date) for the last 90 days, with `cost_usd`, `clicks`, `conversions`, `attributed_revenue`, `cac_usd`, `roas`, and a `data_caveat` column documenting per-channel attribution assumptions\n\nThe DuckDB connection name is `duckdb-default`.\n\n## Layer usage rules (IMPORTANT)\n**Default to `reports.*` for all analysis.** It's pre-cleaned, unit-normalized, and the cross-channel join is already done.\n\nOnly drop to lower layers with an explicit reason:\n- **`staging.*`** - when reports doesn't surface a column you need (e.g. `campaign_name`, `subject_line`, `sessionSource`), or when validating a transform\n- **`raw.*`** - for **data validation** (does cost_micros / 1e6 in raw match cost_usd in staging?), **troubleshooting** (where did a wrong number come from?), or when you genuinely need a column that hasn't been promoted upstream yet\n\nWhen you do drop down, **say so in your response**: \"I'm using `staging.google_ads` here because `reports.channel_daily` doesn't break out by `campaign_name`.\" This keeps the user aware of which surface you're querying.\n\n## Domain glossary (marketing)\n- **CAC** - Customer Acquisition Cost: marketing spend ÷ new customers acquired, over the same period\n- **Blended CAC** - CAC that includes spend from all channels (paid + organic email + organic social). Divide total marketing cost by total new customers.\n- **LTV** - Lifetime Value: total net revenue attributed to a customer over their lifetime\n- **LTV:CAC** - ratio; healthy e-commerce is typically >3\n- **ROAS** - Return on Ad Spend: revenue attributed to ads ÷ ad spend\n- **CTR** - Click-through rate: clicks ÷ impressions\n- **CVR** - Conversion rate: conversions ÷ clicks (for ads) or conversions ÷ sessions (for web)\n- **CPM** - Cost per 1,000 impressions\n- **CPC** - Cost per click\n- **Open rate / Click rate** - Klaviyo: opens ÷ recipients, clicks ÷ recipients (unique by default)\n- **Attribution window** - the time after an ad click / email click during which a conversion is credited to it. GA4 default is 30 days click / 1 day view; Klaviyo default is 5 days.\n\n## Units and time caveats\n- `raw.google_ads_campaigns.cost_micros` is in **micros** - divide by 1,000,000 for USD dollars\n- `raw.klaviyo_campaigns.revenue` is in USD (dollars, not cents)\n- `raw.ga4_traffic.purchaseRevenue` is in the GA4 property's reporting currency (usually USD)\n- All `date` columns are UTC. If your business is US-Eastern, you may see one-day offsets vs. native UI.\n- GA4 data has **24–48 hour delay** for full accuracy. Last two days of `ga4_traffic` are estimates.\n- Klaviyo `opens` are unreliable on iOS (Apple Mail Privacy Protection inflates them). Prefer `clicks` as engagement signal.\n\n## Attribution rules of thumb\n- **Don't double-count revenue.** Google Ads, Klaviyo, and GA4 each have their own attribution model. If you sum attributed revenue across all three it will exceed your actual revenue.\n- **Prefer last-non-direct** for cross-channel attribution when available. GA4's `sessionSource` / `sessionMedium` already apply this.\n- For **paid vs. owned** comparisons, treat Google Ads revenue and Klaviyo revenue as separate, and use GA4's `source/medium` breakdown to sanity-check overlap.\n\n## Query guidelines\n- For all channel-level analytical questions, start with `reports.channel_daily`. CAC, ROAS, and cost are pre-computed; you usually only need to filter and aggregate.\n- For campaign-level breakdowns, drop to `staging.google_ads` (paid) or `staging.klaviyo` (email). Note that `reports.channel_daily` aggregates by channel, not campaign.\n- Always specify the time window explicitly. State which channels count as \"paid\" if computing blended CAC.\n- For cross-channel attribution comparisons, surface the `data_caveat` column from `reports.channel_daily` or restate the assumptions in plain English.\n- Never sum `attributed_revenue` across channels and call it \"total revenue\" - each channel uses its own attribution model. Present per-channel.\n",[5897],{"type":43,"tag":310,"props":5898,"children":5899},{"__ignoreMap":7},[5900,5908,5915,5923,5931,5939,5946,5954,5962,5970,5978,5985,5993,6001,6009,6017,6024,6032,6040,6047,6055,6062,6070,6078,6085,6093,6101,6109,6116,6124,6131,6139,6147,6155,6163,6171,6179,6187,6195,6203,6211,6219,6227,6234,6242,6250,6258,6266,6274,6282,6290,6297,6306,6315,6324,6333,6341,6350,6359,6368,6377,6386],{"type":43,"tag":412,"props":5901,"children":5902},{"class":698,"line":699},[5903],{"type":43,"tag":412,"props":5904,"children":5905},{},[5906],{"type":49,"value":5907},"# AGENTS.md - marketing-analyst-101\n",{"type":43,"tag":412,"props":5909,"children":5910},{"class":698,"line":721},[5911],{"type":43,"tag":412,"props":5912,"children":5913},{"emptyLinePlaceholder":3000},[5914],{"type":49,"value":3003},{"type":43,"tag":412,"props":5916,"children":5917},{"class":698,"line":19},[5918],{"type":43,"tag":412,"props":5919,"children":5920},{},[5921],{"type":49,"value":5922},"## Pipeline overview\n",{"type":43,"tag":412,"props":5924,"children":5925},{"class":698,"line":752},[5926],{"type":43,"tag":412,"props":5927,"children":5928},{},[5929],{"type":49,"value":5930},"This is a marketing-analyst research pipeline. Data lives in DuckDB\n",{"type":43,"tag":412,"props":5932,"children":5933},{"class":698,"line":770},[5934],{"type":43,"tag":412,"props":5935,"children":5936},{},[5937],{"type":49,"value":5938},"(`./marketing-analyst-101/marketing.duckdb`), organized in three layers:\n",{"type":43,"tag":412,"props":5940,"children":5941},{"class":698,"line":788},[5942],{"type":43,"tag":412,"props":5943,"children":5944},{"emptyLinePlaceholder":3000},[5945],{"type":49,"value":3003},{"type":43,"tag":412,"props":5947,"children":5948},{"class":698,"line":806},[5949],{"type":43,"tag":412,"props":5950,"children":5951},{},[5952],{"type":49,"value":5953},"**Raw layer** (`raw.*`) - unmodified from source:\n",{"type":43,"tag":412,"props":5955,"children":5956},{"class":698,"line":824},[5957],{"type":43,"tag":412,"props":5958,"children":5959},{},[5960],{"type":49,"value":5961},"- `raw.google_ads_campaigns` - daily campaign-level ad spend and conversions (from Google Ads). `cost_micros` is in **millionths of a dollar**.\n",{"type":43,"tag":412,"props":5963,"children":5964},{"class":698,"line":842},[5965],{"type":43,"tag":412,"props":5966,"children":5967},{},[5968],{"type":49,"value":5969},"- `raw.klaviyo_campaigns` - campaign-level email metrics and attributed revenue (from Klaviyo)\n",{"type":43,"tag":412,"props":5971,"children":5972},{"class":698,"line":860},[5973],{"type":43,"tag":412,"props":5974,"children":5975},{},[5976],{"type":49,"value":5977},"- `raw.ga4_traffic` - daily sessions by source/medium with conversion events (from GA4)\n",{"type":43,"tag":412,"props":5979,"children":5980},{"class":698,"line":3068},[5981],{"type":43,"tag":412,"props":5982,"children":5983},{"emptyLinePlaceholder":3000},[5984],{"type":49,"value":3003},{"type":43,"tag":412,"props":5986,"children":5987},{"class":698,"line":3077},[5988],{"type":43,"tag":412,"props":5989,"children":5990},{},[5991],{"type":49,"value":5992},"**Staging layer** (`staging.*`) - cleaned, typed, deduplicated, unit-normalized:\n",{"type":43,"tag":412,"props":5994,"children":5995},{"class":698,"line":3086},[5996],{"type":43,"tag":412,"props":5997,"children":5998},{},[5999],{"type":49,"value":6000},"- `staging.google_ads` - adds `cost_usd`, `ctr`, `cvr`, `cpc_usd`; deduped on (campaign_id, date)\n",{"type":43,"tag":412,"props":6002,"children":6003},{"class":698,"line":11},[6004],{"type":43,"tag":412,"props":6005,"children":6006},{},[6007],{"type":49,"value":6008},"- `staging.klaviyo` - adds `open_rate`, `click_rate`, `revenue_per_recipient`; deduped on campaign_id\n",{"type":43,"tag":412,"props":6010,"children":6011},{"class":698,"line":3103},[6012],{"type":43,"tag":412,"props":6013,"children":6014},{},[6015],{"type":49,"value":6016},"- `staging.ga4` - adds a normalized `channel` column (Paid Search / Organic / Email / Direct / Other) and `cvr`\n",{"type":43,"tag":412,"props":6018,"children":6019},{"class":698,"line":3111},[6020],{"type":43,"tag":412,"props":6021,"children":6022},{"emptyLinePlaceholder":3000},[6023],{"type":49,"value":3003},{"type":43,"tag":412,"props":6025,"children":6026},{"class":698,"line":3120},[6027],{"type":43,"tag":412,"props":6028,"children":6029},{},[6030],{"type":49,"value":6031},"**Reports layer** (`reports.*`) - analysis-ready cross-channel surface:\n",{"type":43,"tag":412,"props":6033,"children":6034},{"class":698,"line":3129},[6035],{"type":43,"tag":412,"props":6036,"children":6037},{},[6038],{"type":49,"value":6039},"- `reports.channel_daily` - one row per (channel, date) for the last 90 days, with `cost_usd`, `clicks`, `conversions`, `attributed_revenue`, `cac_usd`, `roas`, and a `data_caveat` column documenting per-channel attribution assumptions\n",{"type":43,"tag":412,"props":6041,"children":6042},{"class":698,"line":3138},[6043],{"type":43,"tag":412,"props":6044,"children":6045},{"emptyLinePlaceholder":3000},[6046],{"type":49,"value":3003},{"type":43,"tag":412,"props":6048,"children":6049},{"class":698,"line":3147},[6050],{"type":43,"tag":412,"props":6051,"children":6052},{},[6053],{"type":49,"value":6054},"The DuckDB connection name is `duckdb-default`.\n",{"type":43,"tag":412,"props":6056,"children":6057},{"class":698,"line":3155},[6058],{"type":43,"tag":412,"props":6059,"children":6060},{"emptyLinePlaceholder":3000},[6061],{"type":49,"value":3003},{"type":43,"tag":412,"props":6063,"children":6064},{"class":698,"line":3164},[6065],{"type":43,"tag":412,"props":6066,"children":6067},{},[6068],{"type":49,"value":6069},"## Layer usage rules (IMPORTANT)\n",{"type":43,"tag":412,"props":6071,"children":6072},{"class":698,"line":3173},[6073],{"type":43,"tag":412,"props":6074,"children":6075},{},[6076],{"type":49,"value":6077},"**Default to `reports.*` for all analysis.** It's pre-cleaned, unit-normalized, and the cross-channel join is already done.\n",{"type":43,"tag":412,"props":6079,"children":6080},{"class":698,"line":3182},[6081],{"type":43,"tag":412,"props":6082,"children":6083},{"emptyLinePlaceholder":3000},[6084],{"type":49,"value":3003},{"type":43,"tag":412,"props":6086,"children":6087},{"class":698,"line":3191},[6088],{"type":43,"tag":412,"props":6089,"children":6090},{},[6091],{"type":49,"value":6092},"Only drop to lower layers with an explicit reason:\n",{"type":43,"tag":412,"props":6094,"children":6095},{"class":698,"line":3199},[6096],{"type":43,"tag":412,"props":6097,"children":6098},{},[6099],{"type":49,"value":6100},"- **`staging.*`** - when reports doesn't surface a column you need (e.g. `campaign_name`, `subject_line`, `sessionSource`), or when validating a transform\n",{"type":43,"tag":412,"props":6102,"children":6103},{"class":698,"line":3208},[6104],{"type":43,"tag":412,"props":6105,"children":6106},{},[6107],{"type":49,"value":6108},"- **`raw.*`** - for **data validation** (does cost_micros / 1e6 in raw match cost_usd in staging?), **troubleshooting** (where did a wrong number come from?), or when you genuinely need a column that hasn't been promoted upstream yet\n",{"type":43,"tag":412,"props":6110,"children":6111},{"class":698,"line":3217},[6112],{"type":43,"tag":412,"props":6113,"children":6114},{"emptyLinePlaceholder":3000},[6115],{"type":49,"value":3003},{"type":43,"tag":412,"props":6117,"children":6118},{"class":698,"line":3226},[6119],{"type":43,"tag":412,"props":6120,"children":6121},{},[6122],{"type":49,"value":6123},"When you do drop down, **say so in your response**: \"I'm using `staging.google_ads` here because `reports.channel_daily` doesn't break out by `campaign_name`.\" This keeps the user aware of which surface you're querying.\n",{"type":43,"tag":412,"props":6125,"children":6126},{"class":698,"line":3235},[6127],{"type":43,"tag":412,"props":6128,"children":6129},{"emptyLinePlaceholder":3000},[6130],{"type":49,"value":3003},{"type":43,"tag":412,"props":6132,"children":6133},{"class":698,"line":3244},[6134],{"type":43,"tag":412,"props":6135,"children":6136},{},[6137],{"type":49,"value":6138},"## Domain glossary (marketing)\n",{"type":43,"tag":412,"props":6140,"children":6141},{"class":698,"line":3253},[6142],{"type":43,"tag":412,"props":6143,"children":6144},{},[6145],{"type":49,"value":6146},"- **CAC** - Customer Acquisition Cost: marketing spend ÷ new customers acquired, over the same period\n",{"type":43,"tag":412,"props":6148,"children":6149},{"class":698,"line":3262},[6150],{"type":43,"tag":412,"props":6151,"children":6152},{},[6153],{"type":49,"value":6154},"- **Blended CAC** - CAC that includes spend from all channels (paid + organic email + organic social). Divide total marketing cost by total new customers.\n",{"type":43,"tag":412,"props":6156,"children":6157},{"class":698,"line":3270},[6158],{"type":43,"tag":412,"props":6159,"children":6160},{},[6161],{"type":49,"value":6162},"- **LTV** - Lifetime Value: total net revenue attributed to a customer over their lifetime\n",{"type":43,"tag":412,"props":6164,"children":6165},{"class":698,"line":3279},[6166],{"type":43,"tag":412,"props":6167,"children":6168},{},[6169],{"type":49,"value":6170},"- **LTV:CAC** - ratio; healthy e-commerce is typically >3\n",{"type":43,"tag":412,"props":6172,"children":6173},{"class":698,"line":3288},[6174],{"type":43,"tag":412,"props":6175,"children":6176},{},[6177],{"type":49,"value":6178},"- **ROAS** - Return on Ad Spend: revenue attributed to ads ÷ ad spend\n",{"type":43,"tag":412,"props":6180,"children":6181},{"class":698,"line":3296},[6182],{"type":43,"tag":412,"props":6183,"children":6184},{},[6185],{"type":49,"value":6186},"- **CTR** - Click-through rate: clicks ÷ impressions\n",{"type":43,"tag":412,"props":6188,"children":6189},{"class":698,"line":3305},[6190],{"type":43,"tag":412,"props":6191,"children":6192},{},[6193],{"type":49,"value":6194},"- **CVR** - Conversion rate: conversions ÷ clicks (for ads) or conversions ÷ sessions (for web)\n",{"type":43,"tag":412,"props":6196,"children":6197},{"class":698,"line":3314},[6198],{"type":43,"tag":412,"props":6199,"children":6200},{},[6201],{"type":49,"value":6202},"- **CPM** - Cost per 1,000 impressions\n",{"type":43,"tag":412,"props":6204,"children":6205},{"class":698,"line":3323},[6206],{"type":43,"tag":412,"props":6207,"children":6208},{},[6209],{"type":49,"value":6210},"- **CPC** - Cost per click\n",{"type":43,"tag":412,"props":6212,"children":6213},{"class":698,"line":3332},[6214],{"type":43,"tag":412,"props":6215,"children":6216},{},[6217],{"type":49,"value":6218},"- **Open rate / Click rate** - Klaviyo: opens ÷ recipients, clicks ÷ recipients (unique by default)\n",{"type":43,"tag":412,"props":6220,"children":6221},{"class":698,"line":3341},[6222],{"type":43,"tag":412,"props":6223,"children":6224},{},[6225],{"type":49,"value":6226},"- **Attribution window** - the time after an ad click / email click during which a conversion is credited to it. GA4 default is 30 days click / 1 day view; Klaviyo default is 5 days.\n",{"type":43,"tag":412,"props":6228,"children":6229},{"class":698,"line":3349},[6230],{"type":43,"tag":412,"props":6231,"children":6232},{"emptyLinePlaceholder":3000},[6233],{"type":49,"value":3003},{"type":43,"tag":412,"props":6235,"children":6236},{"class":698,"line":3358},[6237],{"type":43,"tag":412,"props":6238,"children":6239},{},[6240],{"type":49,"value":6241},"## Units and time caveats\n",{"type":43,"tag":412,"props":6243,"children":6244},{"class":698,"line":3367},[6245],{"type":43,"tag":412,"props":6246,"children":6247},{},[6248],{"type":49,"value":6249},"- `raw.google_ads_campaigns.cost_micros` is in **micros** - divide by 1,000,000 for USD dollars\n",{"type":43,"tag":412,"props":6251,"children":6252},{"class":698,"line":3376},[6253],{"type":43,"tag":412,"props":6254,"children":6255},{},[6256],{"type":49,"value":6257},"- `raw.klaviyo_campaigns.revenue` is in USD (dollars, not cents)\n",{"type":43,"tag":412,"props":6259,"children":6260},{"class":698,"line":3385},[6261],{"type":43,"tag":412,"props":6262,"children":6263},{},[6264],{"type":49,"value":6265},"- `raw.ga4_traffic.purchaseRevenue` is in the GA4 property's reporting currency (usually USD)\n",{"type":43,"tag":412,"props":6267,"children":6268},{"class":698,"line":3393},[6269],{"type":43,"tag":412,"props":6270,"children":6271},{},[6272],{"type":49,"value":6273},"- All `date` columns are UTC. If your business is US-Eastern, you may see one-day offsets vs. native UI.\n",{"type":43,"tag":412,"props":6275,"children":6276},{"class":698,"line":3402},[6277],{"type":43,"tag":412,"props":6278,"children":6279},{},[6280],{"type":49,"value":6281},"- GA4 data has **24–48 hour delay** for full accuracy. Last two days of `ga4_traffic` are estimates.\n",{"type":43,"tag":412,"props":6283,"children":6284},{"class":698,"line":3411},[6285],{"type":43,"tag":412,"props":6286,"children":6287},{},[6288],{"type":49,"value":6289},"- Klaviyo `opens` are unreliable on iOS (Apple Mail Privacy Protection inflates them). Prefer `clicks` as engagement signal.\n",{"type":43,"tag":412,"props":6291,"children":6292},{"class":698,"line":3420},[6293],{"type":43,"tag":412,"props":6294,"children":6295},{"emptyLinePlaceholder":3000},[6296],{"type":49,"value":3003},{"type":43,"tag":412,"props":6298,"children":6300},{"class":698,"line":6299},52,[6301],{"type":43,"tag":412,"props":6302,"children":6303},{},[6304],{"type":49,"value":6305},"## Attribution rules of thumb\n",{"type":43,"tag":412,"props":6307,"children":6309},{"class":698,"line":6308},53,[6310],{"type":43,"tag":412,"props":6311,"children":6312},{},[6313],{"type":49,"value":6314},"- **Don't double-count revenue.** Google Ads, Klaviyo, and GA4 each have their own attribution model. If you sum attributed revenue across all three it will exceed your actual revenue.\n",{"type":43,"tag":412,"props":6316,"children":6318},{"class":698,"line":6317},54,[6319],{"type":43,"tag":412,"props":6320,"children":6321},{},[6322],{"type":49,"value":6323},"- **Prefer last-non-direct** for cross-channel attribution when available. GA4's `sessionSource` / `sessionMedium` already apply this.\n",{"type":43,"tag":412,"props":6325,"children":6327},{"class":698,"line":6326},55,[6328],{"type":43,"tag":412,"props":6329,"children":6330},{},[6331],{"type":49,"value":6332},"- For **paid vs. owned** comparisons, treat Google Ads revenue and Klaviyo revenue as separate, and use GA4's `source/medium` breakdown to sanity-check overlap.\n",{"type":43,"tag":412,"props":6334,"children":6336},{"class":698,"line":6335},56,[6337],{"type":43,"tag":412,"props":6338,"children":6339},{"emptyLinePlaceholder":3000},[6340],{"type":49,"value":3003},{"type":43,"tag":412,"props":6342,"children":6344},{"class":698,"line":6343},57,[6345],{"type":43,"tag":412,"props":6346,"children":6347},{},[6348],{"type":49,"value":6349},"## Query guidelines\n",{"type":43,"tag":412,"props":6351,"children":6353},{"class":698,"line":6352},58,[6354],{"type":43,"tag":412,"props":6355,"children":6356},{},[6357],{"type":49,"value":6358},"- For all channel-level analytical questions, start with `reports.channel_daily`. CAC, ROAS, and cost are pre-computed; you usually only need to filter and aggregate.\n",{"type":43,"tag":412,"props":6360,"children":6362},{"class":698,"line":6361},59,[6363],{"type":43,"tag":412,"props":6364,"children":6365},{},[6366],{"type":49,"value":6367},"- For campaign-level breakdowns, drop to `staging.google_ads` (paid) or `staging.klaviyo` (email). Note that `reports.channel_daily` aggregates by channel, not campaign.\n",{"type":43,"tag":412,"props":6369,"children":6371},{"class":698,"line":6370},60,[6372],{"type":43,"tag":412,"props":6373,"children":6374},{},[6375],{"type":49,"value":6376},"- Always specify the time window explicitly. State which channels count as \"paid\" if computing blended CAC.\n",{"type":43,"tag":412,"props":6378,"children":6380},{"class":698,"line":6379},61,[6381],{"type":43,"tag":412,"props":6382,"children":6383},{},[6384],{"type":49,"value":6385},"- For cross-channel attribution comparisons, surface the `data_caveat` column from `reports.channel_daily` or restate the assumptions in plain English.\n",{"type":43,"tag":412,"props":6387,"children":6389},{"class":698,"line":6388},62,[6390],{"type":43,"tag":412,"props":6391,"children":6392},{},[6393],{"type":49,"value":6394},"- Never sum `attributed_revenue` across channels and call it \"total revenue\" - each channel uses its own attribution model. Present per-channel.\n",{"type":43,"tag":52,"props":6396,"children":6397},{},[6398,6400,6405,6407,6412],{"type":49,"value":6399},"When the agent is done, your workspace has two ",{"type":43,"tag":310,"props":6401,"children":6403},{"className":6402},[],[6404],{"type":49,"value":2562},{"type":49,"value":6406}," files working in tandem: the root one with Bruin rules (applies everywhere), and ",{"type":43,"tag":310,"props":6408,"children":6410},{"className":6409},[],[6411],{"type":49,"value":5523},{"type":49,"value":6413}," with marketing domain context (applies when the agent is working inside this pipeline). Restart your AI coding tool (new Claude Code session / reopen Cursor) so it picks up the new file.",{"type":43,"tag":44,"props":6415,"children":6417},{"id":6416},"_3-sanity-check-the-context",[6418],{"type":49,"value":6419},"3. Sanity-check the context",{"type":43,"tag":52,"props":6421,"children":6422},{},[6423,6425,6430],{"type":49,"value":6424},"Ask your agent a scoping question that should only be answerable if it read ",{"type":43,"tag":310,"props":6426,"children":6428},{"className":6427},[],[6429],{"type":49,"value":2562},{"type":49,"value":325},{"type":43,"tag":301,"props":6432,"children":6433},{},[6434],{"type":43,"tag":52,"props":6435,"children":6436},{},[6437,6439,6444],{"type":49,"value":6438},"In this project, what unit is ",{"type":43,"tag":310,"props":6440,"children":6442},{"className":6441},[],[6443],{"type":49,"value":4421},{"type":49,"value":6445}," stored in, and how should I convert it to USD?",{"type":43,"tag":52,"props":6447,"children":6448},{},[6449,6451,6456,6458,6463],{"type":49,"value":6450},"The agent should answer with \"micros - divide by 1,000,000\". If it fumbles or asks you instead, the pipeline-level ",{"type":43,"tag":310,"props":6452,"children":6454},{"className":6453},[],[6455],{"type":49,"value":2562},{"type":49,"value":6457}," wasn't loaded - restart the session, and make sure the agent is working inside the ",{"type":43,"tag":310,"props":6459,"children":6461},{"className":6460},[],[6462],{"type":49,"value":3451},{"type":49,"value":6464}," folder.",{"type":43,"tag":44,"props":6466,"children":6467},{"id":1070},[6468],{"type":49,"value":1073},{"type":43,"tag":52,"props":6470,"children":6471},{},[6472,6474,6479,6481,6486],{"type":49,"value":6473},"Your agent now has three layers of context: workspace-level Bruin rules (root ",{"type":43,"tag":310,"props":6475,"children":6477},{"className":6476},[],[6478],{"type":49,"value":2562},{"type":49,"value":6480},"), per-column schema descriptions (auto-generated into the asset YAML), and pipeline-specific marketing knowledge (",{"type":43,"tag":310,"props":6482,"children":6484},{"className":6483},[],[6485],{"type":49,"value":5523},{"type":49,"value":6487},"). Every question you ask from here on out is reasoned against this stack - so you won't get CAC numbers off by 1,000,000x, or revenue double-counted across channels. You've just done what takes a new human analyst weeks of onboarding.",{"type":43,"tag":1087,"props":6489,"children":6490},{},[6491],{"type":49,"value":1091},{"title":7,"searchDepth":721,"depth":721,"links":6493},[6494,6495,6496,6497,6498,6499],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":5540,"depth":721,"text":5543},{"id":5817,"depth":721,"text":5820},{"id":6416,"depth":721,"text":6419},{"id":1070,"depth":721,"text":1073},"content:tutorials:marketing-analyst-101:enhance-context.md","tutorials/marketing-analyst-101/enhance-context.md","tutorials/marketing-analyst-101/enhance-context",{"_path":6504,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":6505,"description":6506,"date":10,"readingTime":824,"category":12,"tags":6507,"difficulty":18,"module":5,"step":788,"journeys":6508,"roles":6509,"learnMore":6510,"author":6515,"body":6516,"_type":1103,"_id":7187,"_source":1105,"_file":7188,"_stem":7189,"_extension":1108},"/tutorials/marketing-analyst-101/ask-the-question","Ask the Real Marketing Question","Put the stack to work. Combine Google Ads, Klaviyo, and GA4 to answer the kind of cross-channel question your BI tool can't handle.",[14,15,1969,16,17],[21],[23,24],[6511,6514],{"label":6512,"url":6513},"Bruin query command","https://getbruin.com/docs/bruin/commands/query.html",{"label":5389,"url":5390},{"name":36,"role":37,"image":38},{"type":40,"children":6517,"toc":7177},[6518,6522,6527,6531,6543,6569,6575,6580,6701,6707,6712,6815,6841,6847,6852,6881,6910,6947,6969,6974,6980,6985,7031,7044,7050,7116,7120,7125,7133],{"type":43,"tag":44,"props":6519,"children":6520},{"id":46},[6521],{"type":49,"value":50},{"type":43,"tag":52,"props":6523,"children":6524},{},[6525],{"type":49,"value":6526},"Ask your AI analyst the signature question this module was built for - one that requires all three data sources at once. Then learn the habits that get you consistently good answers.",{"type":43,"tag":44,"props":6528,"children":6529},{"id":59},[6530],{"type":49,"value":62},{"type":43,"tag":52,"props":6532,"children":6533},{},[6534,6536,6541],{"type":49,"value":6535},"The whole point of the last five steps - the CLI, the MCP, the ingestion, the staging + reports layers, the context file - was to make ",{"type":43,"tag":77,"props":6537,"children":6538},{},[6539],{"type":49,"value":6540},"this step",{"type":49,"value":6542}," possible: asking a question that spans paid, email, and web in one breath, and getting a worked answer in 30 seconds. Most marketing teams need a custom BI workstream to answer this. You just built it in a morning.",{"type":43,"tag":52,"props":6544,"children":6545},{},[6546,6548,6553,6555,6560,6562,6567],{"type":49,"value":6547},"You built ",{"type":43,"tag":310,"props":6549,"children":6551},{"className":6550},[],[6552],{"type":49,"value":4968},{"type":49,"value":6554}," in Step 4 specifically so the AI doesn't have to remember to divide ",{"type":43,"tag":310,"props":6556,"children":6558},{"className":6557},[],[6559],{"type":49,"value":4421},{"type":49,"value":6561}," by 1,000,000 or pick an attribution model every session. Your AGENTS.md tells the agent to default to ",{"type":43,"tag":310,"props":6563,"children":6565},{"className":6564},[],[6566],{"type":49,"value":4402},{"type":49,"value":6568}," - so this question is mostly a SELECT against a pre-cleaned, pre-unioned table.",{"type":43,"tag":44,"props":6570,"children":6572},{"id":6571},"the-signature-question",[6573],{"type":49,"value":6574},"The signature question",{"type":43,"tag":52,"props":6576,"children":6577},{},[6578],{"type":49,"value":6579},"Paste this into your AI tool:",{"type":43,"tag":301,"props":6581,"children":6582},{},[6583,6601,6644,6671,6696],{"type":43,"tag":52,"props":6584,"children":6585},{},[6586,6587,6592,6594,6599],{"type":49,"value":960},{"type":43,"tag":310,"props":6588,"children":6590},{"className":6589},[],[6591],{"type":49,"value":4968},{"type":49,"value":6593},", compute ",{"type":43,"tag":77,"props":6595,"children":6596},{},[6597],{"type":49,"value":6598},"blended CAC and ROAS by channel",{"type":49,"value":6600}," for the last 90 days available. Break down:",{"type":43,"tag":327,"props":6602,"children":6603},{},[6604,6626,6635],{"type":43,"tag":73,"props":6605,"children":6606},{},[6607,6611,6613,6619,6620],{"type":43,"tag":77,"props":6608,"children":6609},{},[6610],{"type":49,"value":4990},{"type":49,"value":6612}," - sum cost_usd and conversions, then ",{"type":43,"tag":310,"props":6614,"children":6616},{"className":6615},[],[6617],{"type":49,"value":6618},"cac = cost_usd / conversions",{"type":49,"value":451},{"type":43,"tag":310,"props":6621,"children":6623},{"className":6622},[],[6624],{"type":49,"value":6625},"roas = attributed_revenue / cost_usd",{"type":43,"tag":73,"props":6627,"children":6628},{},[6629,6633],{"type":43,"tag":77,"props":6630,"children":6631},{},[6632],{"type":49,"value":5015},{"type":49,"value":6634}," - sum attributed_revenue (cost is $0); show revenue-per-thousand-recipients as a soft proxy for efficiency",{"type":43,"tag":73,"props":6636,"children":6637},{},[6638,6642],{"type":43,"tag":77,"props":6639,"children":6640},{},[6641],{"type":49,"value":5053},{"type":49,"value":6643}," - sum sessions, conversions, attributed_revenue (cost is $0); show conversion rate",{"type":43,"tag":52,"props":6645,"children":6646},{},[6647,6649,6654,6656,6662,6664,6669],{"type":49,"value":6648},"Then produce a ",{"type":43,"tag":77,"props":6650,"children":6651},{},[6652],{"type":49,"value":6653},"blended CAC",{"type":49,"value":6655}," for the period: ",{"type":43,"tag":310,"props":6657,"children":6659},{"className":6658},[],[6660],{"type":49,"value":6661},"total cost_usd across all channels / total conversions across all channels",{"type":49,"value":6663}," - and call out that this only counts conversions captured in ",{"type":43,"tag":310,"props":6665,"children":6667},{"className":6666},[],[6668],{"type":49,"value":4968},{"type":49,"value":6670},", so it understates true acquisition if email or organic conversion tracking is partial.",{"type":43,"tag":52,"props":6672,"children":6673},{},[6674,6676,6681,6683,6688,6689,6694],{"type":49,"value":6675},"Default to ",{"type":43,"tag":310,"props":6677,"children":6679},{"className":6678},[],[6680],{"type":49,"value":4968},{"type":49,"value":6682},". Only drop into ",{"type":43,"tag":310,"props":6684,"children":6686},{"className":6685},[],[6687],{"type":49,"value":4386},{"type":49,"value":4735},{"type":43,"tag":310,"props":6690,"children":6692},{"className":6691},[],[6693],{"type":49,"value":932},{"type":49,"value":6695}," if the user is asking for a column that hasn't been surfaced upstream (e.g. campaign-level breakouts), and tell me when you do.",{"type":43,"tag":52,"props":6697,"children":6698},{},[6699],{"type":49,"value":6700},"Show me the SQL first. State the attribution caveats in plain English. Only execute once I approve the plan.",{"type":43,"tag":44,"props":6702,"children":6704},{"id":6703},"what-to-look-for-in-the-agents-plan",[6705],{"type":49,"value":6706},"What to look for in the agent's plan",{"type":43,"tag":52,"props":6708,"children":6709},{},[6710],{"type":49,"value":6711},"Before approving the SQL, check that the agent:",{"type":43,"tag":69,"props":6713,"children":6714},{},[6715,6738,6764,6783,6795],{"type":43,"tag":73,"props":6716,"children":6717},{},[6718,6720,6725,6727,6732,6733],{"type":49,"value":6719},"Queries ",{"type":43,"tag":310,"props":6721,"children":6723},{"className":6722},[],[6724],{"type":49,"value":4968},{"type":49,"value":6726}," as the primary source - not ",{"type":43,"tag":310,"props":6728,"children":6730},{"className":6729},[],[6731],{"type":49,"value":932},{"type":49,"value":4735},{"type":43,"tag":310,"props":6734,"children":6736},{"className":6735},[],[6737],{"type":49,"value":4386},{"type":43,"tag":73,"props":6739,"children":6740},{},[6741,6743,6748,6750,6755,6757,6762],{"type":49,"value":6742},"Uses the pre-computed ",{"type":43,"tag":310,"props":6744,"children":6746},{"className":6745},[],[6747],{"type":49,"value":4597},{"type":49,"value":6749}," (not ",{"type":43,"tag":310,"props":6751,"children":6753},{"className":6752},[],[6754],{"type":49,"value":4421},{"type":49,"value":6756},") and ",{"type":43,"tag":310,"props":6758,"children":6760},{"className":6759},[],[6761],{"type":49,"value":5093},{"type":49,"value":6763}," columns",{"type":43,"tag":73,"props":6765,"children":6766},{},[6767,6769,6773,6775,6781],{"type":49,"value":6768},"Does ",{"type":43,"tag":77,"props":6770,"children":6771},{},[6772],{"type":49,"value":5883},{"type":49,"value":6774}," sum ",{"type":43,"tag":310,"props":6776,"children":6778},{"className":6777},[],[6779],{"type":49,"value":6780},"attributed_revenue",{"type":49,"value":6782}," across all three channels and call it \"total marketing revenue\" (that's double-counting - it should sum within a channel and present per-channel)",{"type":43,"tag":73,"props":6784,"children":6785},{},[6786,6788,6793],{"type":49,"value":6787},"Surfaces the ",{"type":43,"tag":310,"props":6789,"children":6791},{"className":6790},[],[6792],{"type":49,"value":5117},{"type":49,"value":6794}," column or restates the attribution assumptions in plain English",{"type":43,"tag":73,"props":6796,"children":6797},{},[6798,6800,6805,6807,6813],{"type":49,"value":6799},"States explicitly if it does drop into ",{"type":43,"tag":310,"props":6801,"children":6803},{"className":6802},[],[6804],{"type":49,"value":4386},{"type":49,"value":6806}," (e.g. for ",{"type":43,"tag":310,"props":6808,"children":6810},{"className":6809},[],[6811],{"type":49,"value":6812},"campaign_name",{"type":49,"value":6814}," breakouts) and why",{"type":43,"tag":52,"props":6816,"children":6817},{},[6818,6820,6825,6827,6839],{"type":49,"value":6819},"If the agent reaches into ",{"type":43,"tag":310,"props":6821,"children":6823},{"className":6822},[],[6824],{"type":49,"value":4561},{"type":49,"value":6826}," to recompute things, push back: ",{"type":43,"tag":203,"props":6828,"children":6829},{},[6830,6832,6837],{"type":49,"value":6831},"\"",{"type":43,"tag":310,"props":6833,"children":6835},{"className":6834},[],[6836],{"type":49,"value":4968},{"type":49,"value":6838}," already has cost in USD and CAC computed - use that.\"",{"type":49,"value":6840}," This is the discipline that makes the layered pipeline pay off.",{"type":43,"tag":44,"props":6842,"children":6844},{"id":6843},"follow-up-questions-to-try",[6845],{"type":49,"value":6846},"Follow-up questions to try",{"type":43,"tag":52,"props":6848,"children":6849},{},[6850],{"type":49,"value":6851},"Once the first question runs clean, keep pulling threads - tap any prompt below to copy it:",{"type":43,"tag":301,"props":6853,"children":6854},{},[6855],{"type":43,"tag":52,"props":6856,"children":6857},{},[6858,6860,6865,6867,6872,6874,6879],{"type":49,"value":6859},"From ",{"type":43,"tag":310,"props":6861,"children":6863},{"className":6862},[],[6864],{"type":49,"value":4968},{"type":49,"value":6866},", which day-of-week has the highest email ",{"type":43,"tag":310,"props":6868,"children":6870},{"className":6869},[],[6871],{"type":49,"value":6780},{"type":49,"value":6873}," per send? Compare against Paid Search ",{"type":43,"tag":310,"props":6875,"children":6877},{"className":6876},[],[6878],{"type":49,"value":5093},{"type":49,"value":6880}," by day-of-week - do they correlate?",{"type":43,"tag":301,"props":6882,"children":6883},{},[6884],{"type":43,"tag":52,"props":6885,"children":6886},{},[6887,6889,6894,6896,6901,6903,6908],{"type":49,"value":6888},"Compute ROAS by campaign - drop down to ",{"type":43,"tag":310,"props":6890,"children":6892},{"className":6891},[],[6893],{"type":49,"value":4547},{"type":49,"value":6895}," for this since ",{"type":43,"tag":310,"props":6897,"children":6899},{"className":6898},[],[6900],{"type":49,"value":4968},{"type":49,"value":6902}," is aggregated by channel, not campaign. Group by ",{"type":43,"tag":310,"props":6904,"children":6906},{"className":6905},[],[6907],{"type":49,"value":6812},{"type":49,"value":6909}," and sort descending. Flag any campaign with ROAS \u003C 1.0 as candidates to pause.",{"type":43,"tag":301,"props":6911,"children":6912},{},[6913],{"type":43,"tag":52,"props":6914,"children":6915},{},[6916,6918,6923,6925,6930,6932,6938,6939,6945],{"type":49,"value":6917},"Cross-validate Google Ads vs GA4: for ",{"type":43,"tag":310,"props":6919,"children":6921},{"className":6920},[],[6922],{"type":49,"value":4968},{"type":49,"value":6924}," Paid Search rows, do the conversion counts roughly match ",{"type":43,"tag":310,"props":6926,"children":6928},{"className":6927},[],[6929],{"type":49,"value":4767},{"type":49,"value":6931}," rows where ",{"type":43,"tag":310,"props":6933,"children":6935},{"className":6934},[],[6936],{"type":49,"value":6937},"sessionSource = 'google'",{"type":49,"value":2955},{"type":43,"tag":310,"props":6940,"children":6942},{"className":6941},[],[6943],{"type":49,"value":6944},"sessionMedium = 'cpc'",{"type":49,"value":6946},"? If they disagree by more than 20%, explain why (likely attribution-window differences).",{"type":43,"tag":301,"props":6948,"children":6949},{},[6950],{"type":43,"tag":52,"props":6951,"children":6952},{},[6953,6954,6959,6961,6967],{"type":49,"value":6859},{"type":43,"tag":310,"props":6955,"children":6957},{"className":6956},[],[6958],{"type":49,"value":4648},{"type":49,"value":6960},", which campaign had the highest ",{"type":43,"tag":310,"props":6962,"children":6964},{"className":6963},[],[6965],{"type":49,"value":6966},"revenue_per_recipient",{"type":49,"value":6968}," in the last 30 days? What was the subject line? (ask the user for this - we don't have it in the raw data)",{"type":43,"tag":52,"props":6970,"children":6971},{},[6972],{"type":49,"value":6973},"Each of these takes a marketing analyst 30–60 minutes manually. Your agent does them in the time it takes to write the prompt.",{"type":43,"tag":44,"props":6975,"children":6977},{"id":6976},"save-the-good-queries",[6978],{"type":49,"value":6979},"Save the good queries",{"type":43,"tag":52,"props":6981,"children":6982},{},[6983],{"type":49,"value":6984},"When the agent writes a SQL query you like, ask it to save it as a Bruin SQL asset in the reports layer:",{"type":43,"tag":301,"props":6986,"children":6987},{},[6988],{"type":43,"tag":52,"props":6989,"children":6990},{},[6991,6993,6999,7001,7007,7009,7015,7017,7022,7024,7029],{"type":49,"value":6992},"Take that query and save it as ",{"type":43,"tag":310,"props":6994,"children":6996},{"className":6995},[],[6997],{"type":49,"value":6998},"marketing-analyst-101/assets/reports/blended_cac_by_channel.sql",{"type":49,"value":7000}," materialized as ",{"type":43,"tag":310,"props":7002,"children":7004},{"className":7003},[],[7005],{"type":49,"value":7006},"reports.blended_cac_by_channel",{"type":49,"value":7008}," (materialization ",{"type":43,"tag":310,"props":7010,"children":7012},{"className":7011},[],[7013],{"type":49,"value":7014},"view",{"type":49,"value":7016},"). Add a top-level description, column descriptions, the attribution assumptions in a ",{"type":43,"tag":310,"props":7018,"children":7020},{"className":7019},[],[7021],{"type":49,"value":5117},{"type":49,"value":7023}," comment, and a ",{"type":43,"tag":310,"props":7025,"children":7027},{"className":7026},[],[7028],{"type":49,"value":5130},{"type":49,"value":7030}," tag.",{"type":43,"tag":52,"props":7032,"children":7033},{},[7034,7036,7042],{"type":49,"value":7035},"Now it's part of your pipeline. Next run of ",{"type":43,"tag":310,"props":7037,"children":7039},{"className":7038},[],[7040],{"type":49,"value":7041},"bruin run",{"type":49,"value":7043},", the view is rebuilt from fresh data. You're not just asking questions - you're growing your reports layer with every analysis you save.",{"type":43,"tag":44,"props":7045,"children":7047},{"id":7046},"habits-for-getting-consistently-good-answers",[7048],{"type":49,"value":7049},"Habits for getting consistently good answers",{"type":43,"tag":69,"props":7051,"children":7052},{},[7053,7063,7073,7083,7093],{"type":43,"tag":73,"props":7054,"children":7055},{},[7056,7061],{"type":43,"tag":77,"props":7057,"children":7058},{},[7059],{"type":49,"value":7060},"Ask for the plan before the result.",{"type":49,"value":7062}," Prompts that include \"show me the SQL first and explain the logic\" catch mistakes cheap.",{"type":43,"tag":73,"props":7064,"children":7065},{},[7066,7071],{"type":43,"tag":77,"props":7067,"children":7068},{},[7069],{"type":49,"value":7070},"Be specific about time windows.",{"type":49,"value":7072}," \"Recent\" is ambiguous; \"last 30 days ending today\" is not.",{"type":43,"tag":73,"props":7074,"children":7075},{},[7076,7081],{"type":43,"tag":77,"props":7077,"children":7078},{},[7079],{"type":49,"value":7080},"Name the attribution model.",{"type":49,"value":7082}," Most wrong answers in marketing analytics come from mismatched attribution. Tell the agent which model to use (last-touch, first-touch, last-non-direct, etc.).",{"type":43,"tag":73,"props":7084,"children":7085},{},[7086,7091],{"type":43,"tag":77,"props":7087,"children":7088},{},[7089],{"type":49,"value":7090},"When in doubt, ask for counts first.",{"type":49,"value":7092}," \"How many sessions match X?\" before \"here are all the sessions matching X\" - saves you from chasing ghost results.",{"type":43,"tag":73,"props":7094,"children":7095},{},[7096,7107,7109,7114],{"type":43,"tag":77,"props":7097,"children":7098},{},[7099,7101,7106],{"type":49,"value":7100},"Iterate on ",{"type":43,"tag":310,"props":7102,"children":7104},{"className":7103},[],[7105],{"type":49,"value":2562},{"type":49,"value":595},{"type":49,"value":7108}," Every time the agent gets something wrong for a domain reason, add a line to ",{"type":43,"tag":310,"props":7110,"children":7112},{"className":7111},[],[7113],{"type":49,"value":2562},{"type":49,"value":7115}," so it doesn't repeat the mistake.",{"type":43,"tag":44,"props":7117,"children":7118},{"id":1070},[7119],{"type":49,"value":1073},{"type":43,"tag":52,"props":7121,"children":7122},{},[7123],{"type":49,"value":7124},"You built a complete marketing-analyst workstation - ingest, database, context, agent - on a single laptop, in one sitting, without writing SQL or Python yourself. The stack is yours to keep. Swap in different date ranges, add Meta Ads or Shopify, plug in your CRM. Every addition follows the same pattern: prompt the agent, it runs Bruin, the data lands.",{"type":43,"tag":52,"props":7126,"children":7127},{},[7128],{"type":43,"tag":77,"props":7129,"children":7130},{},[7131],{"type":49,"value":7132},"Where to go next",{"type":43,"tag":69,"props":7134,"children":7135},{},[7136,7150,7164],{"type":43,"tag":73,"props":7137,"children":7138},{},[7139,7148],{"type":43,"tag":77,"props":7140,"children":7141},{},[7142],{"type":43,"tag":169,"props":7143,"children":7145},{"href":7144},"/learn/ecommerce-pipeline",[7146],{"type":49,"value":7147},"Shopify Data Pipeline",{"type":49,"value":7149}," - go deeper on the e-commerce side with a cloud database and a full data-cleanup layer",{"type":43,"tag":73,"props":7151,"children":7152},{},[7153,7162],{"type":43,"tag":77,"props":7154,"children":7155},{},[7156],{"type":43,"tag":169,"props":7157,"children":7159},{"href":7158},"/learn/ai-data-analyst",[7160],{"type":49,"value":7161},"Build an AI Data Analyst",{"type":49,"value":7163}," - the full deep dive on the MCP + AGENTS.md pattern for any data domain",{"type":43,"tag":73,"props":7165,"children":7166},{},[7167,7175],{"type":43,"tag":77,"props":7168,"children":7169},{},[7170],{"type":43,"tag":169,"props":7171,"children":7172},{"href":2534},[7173],{"type":49,"value":7174},"Using Bruin Templates",{"type":49,"value":7176}," - skip scaffolding entirely with pre-built pipelines",{"title":7,"searchDepth":721,"depth":721,"links":7178},[7179,7180,7181,7182,7183,7184,7185,7186],{"id":46,"depth":721,"text":50},{"id":59,"depth":721,"text":62},{"id":6571,"depth":721,"text":6574},{"id":6703,"depth":721,"text":6706},{"id":6843,"depth":721,"text":6846},{"id":6976,"depth":721,"text":6979},{"id":7046,"depth":721,"text":7049},{"id":1070,"depth":721,"text":1073},"content:tutorials:marketing-analyst-101:ask-the-question.md","tutorials/marketing-analyst-101/ask-the-question.md","tutorials/marketing-analyst-101/ask-the-question",1777389165890]